Merge "Doze brightness float" into main
diff --git a/core/java/android/app/Person.java b/core/java/android/app/Person.java
index 96f6f4e..c7432c5 100644
--- a/core/java/android/app/Person.java
+++ b/core/java/android/app/Person.java
@@ -189,10 +189,8 @@
*/
public void visitUris(@NonNull Consumer<Uri> visitor) {
visitor.accept(getIconUri());
- if (Flags.visitPersonUri()) {
- if (mUri != null && !mUri.isEmpty()) {
- visitor.accept(Uri.parse(mUri));
- }
+ if (mUri != null && !mUri.isEmpty()) {
+ visitor.accept(Uri.parse(mUri));
}
}
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index 32d1964..ca6d86a 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -16,6 +16,8 @@
package android.content;
+import static android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM;
+
import android.annotation.FlaggedApi;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -23,6 +25,9 @@
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.IApplicationThread;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.os.Handler;
@@ -65,6 +70,11 @@
* {@link android.app.PendingIntent#getIntentSender() PendingIntent.getIntentSender()}.
*/
public class IntentSender implements Parcelable {
+ /** If enabled consider the deprecated @hide method as removed. */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = VANILLA_ICE_CREAM)
+ private static final long REMOVE_HIDDEN_SEND_INTENT_METHOD = 356174596;
+
private static final Bundle SEND_INTENT_DEFAULT_OPTIONS =
ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT).toBundle();
@@ -220,6 +230,44 @@
* original Intent. Use {@code null} to not modify the original Intent.
* @param onFinished The object to call back on when the send has
* completed, or {@code null} for no callback.
+ * @param handler Handler identifying the thread on which the callback
+ * should happen. If {@code null}, the callback will happen from the thread
+ * pool of the process.
+ * @param options Additional options the caller would like to provide to modify the sending
+ * behavior. Typically built from using {@link ActivityOptions} to apply to an activity start.
+ *
+ * @throws SendIntentException Throws CanceledIntentException if the IntentSender
+ * is no longer allowing more intents to be sent through it.
+ *
+ * @deprecated use {@link #sendIntent(Context, int, Intent, String, Bundle, Executor,
+ * OnFinished)}
+ *
+ * @hide
+ */
+ @Deprecated public void sendIntent(Context context, int code, Intent intent,
+ OnFinished onFinished, Handler handler, String requiredPermission,
+ @Nullable Bundle options)
+ throws SendIntentException {
+ if (CompatChanges.isChangeEnabled(REMOVE_HIDDEN_SEND_INTENT_METHOD)) {
+ throw new NoSuchMethodError("This overload of sendIntent was removed.");
+ }
+ sendIntent(context, code, intent, requiredPermission, options,
+ handler == null ? null : handler::post, onFinished);
+ }
+
+ /**
+ * Perform the operation associated with this IntentSender, allowing the
+ * caller to specify information about the Intent to use and be notified
+ * when the send has completed.
+ *
+ * @param context The Context of the caller. This may be {@code null} if
+ * <var>intent</var> is also {@code null}.
+ * @param code Result code to supply back to the IntentSender's target.
+ * @param intent Additional Intent data. See {@link Intent#fillIn
+ * Intent.fillIn()} for information on how this is applied to the
+ * original Intent. Use {@code null} to not modify the original Intent.
+ * @param onFinished The object to call back on when the send has
+ * completed, or {@code null} for no callback.
* @param executor Executor identifying the thread on which the callback
* should happen. If {@code null}, the callback will happen from the thread
* pool of the process.
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 136c45d..47096db 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -434,7 +434,6 @@
@RavenwoodThrow
private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
@FastNative
- @RavenwoodThrow
private static native void nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
private static native byte[] nativeCreateByteArray(long nativePtr);
@@ -456,7 +455,6 @@
@RavenwoodThrow
private static native IBinder nativeReadStrongBinder(long nativePtr);
@FastNative
- @RavenwoodThrow
private static native FileDescriptor nativeReadFileDescriptor(long nativePtr);
private static native long nativeCreate();
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 71957ee..464df23 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -381,6 +381,8 @@
}
private static void closeInternal$ravenwood(FileDescriptor fd) {
+ // Desktop JVM doesn't have FileDescriptor.close(), so we'll need to go to the ravenwood
+ // side to close it.
native_close$ravenwood(fd);
}
diff --git a/core/java/android/view/InputEventAssigner.java b/core/java/android/view/InputEventAssigner.java
index 7fac6c5..30d9aaa 100644
--- a/core/java/android/view/InputEventAssigner.java
+++ b/core/java/android/view/InputEventAssigner.java
@@ -17,7 +17,8 @@
package android.view;
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
-import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+import static android.view.InputDevice.SOURCE_CLASS_POINTER;
+import static android.view.InputDevice.SOURCE_CLASS_POSITION;
/**
* Process input events and assign input event id to a specific frame.
@@ -64,18 +65,19 @@
public int processEvent(InputEvent event) {
if (event instanceof MotionEvent) {
MotionEvent motionEvent = (MotionEvent) event;
- if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN)) {
+ if (motionEvent.isFromSource(SOURCE_CLASS_POINTER) || motionEvent.isFromSource(
+ SOURCE_CLASS_POSITION)) {
final int action = motionEvent.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
mHasUnprocessedDown = true;
mDownEventId = event.getId();
}
- if (mHasUnprocessedDown && action == MotionEvent.ACTION_MOVE) {
- return mDownEventId;
- }
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mHasUnprocessedDown = false;
}
+ if (mHasUnprocessedDown) {
+ return mDownEventId;
+ }
}
}
return event.getId();
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index a4ca55e..2f515fe 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -201,7 +201,9 @@
* @param exceptionHandler an optional {@link RemoteException} handler
*/
@AnyThread
- @RequiresPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @RequiresPermission(allOf = {
+ Manifest.permission.INTERNAL_SYSTEM_WINDOW,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
static void removeImeSurface(int displayId,
@Nullable Consumer<RemoteException> exceptionHandler) {
final IInputMethodManager service = getService();
@@ -441,7 +443,9 @@
}
@AnyThread
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @RequiresPermission(allOf = {
+ Manifest.permission.WRITE_SECURE_SETTINGS,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
static void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
final IInputMethodManager service = getService();
if (service == null) {
@@ -469,7 +473,9 @@
}
@AnyThread
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @RequiresPermission(allOf = {
+ Manifest.permission.WRITE_SECURE_SETTINGS,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
static void onImeSwitchButtonClickFromSystem(int displayId) {
final IInputMethodManager service = getService();
if (service == null) {
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
index fa51957..23a1224 100644
--- a/core/java/android/window/TaskFragmentInfo.java
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -102,6 +102,8 @@
@NonNull
private final Point mMinimumDimensions = new Point();
+ private final boolean mIsTopNonFishingChild;
+
/** @hide */
public TaskFragmentInfo(
@NonNull IBinder fragmentToken, @NonNull WindowContainerToken token,
@@ -110,7 +112,7 @@
@NonNull List<IBinder> inRequestedTaskFragmentActivities,
@NonNull Point positionInParent, boolean isTaskClearedForReuse,
boolean isTaskFragmentClearedForPip, boolean isClearedForReorderActivityToFront,
- @NonNull Point minimumDimensions) {
+ @NonNull Point minimumDimensions, boolean isTopNonFinishingChild) {
mFragmentToken = requireNonNull(fragmentToken);
mToken = requireNonNull(token);
mConfiguration.setTo(configuration);
@@ -123,6 +125,7 @@
mIsTaskFragmentClearedForPip = isTaskFragmentClearedForPip;
mIsClearedForReorderActivityToFront = isClearedForReorderActivityToFront;
mMinimumDimensions.set(minimumDimensions);
+ mIsTopNonFishingChild = isTopNonFinishingChild;
}
@NonNull
@@ -212,6 +215,16 @@
}
/**
+ * Indicates that this TaskFragment is the top non-finishing child of its parent container
+ * among all Activities and TaskFragment siblings.
+ *
+ * @hide
+ */
+ public boolean isTopNonFinishingChild() {
+ return mIsTopNonFishingChild;
+ }
+
+ /**
* Returns {@code true} if the parameters that are important for task fragment organizers are
* equal between this {@link TaskFragmentInfo} and {@param that}.
* Note that this method is usually called with
@@ -236,7 +249,8 @@
&& mIsTaskClearedForReuse == that.mIsTaskClearedForReuse
&& mIsTaskFragmentClearedForPip == that.mIsTaskFragmentClearedForPip
&& mIsClearedForReorderActivityToFront == that.mIsClearedForReorderActivityToFront
- && mMinimumDimensions.equals(that.mMinimumDimensions);
+ && mMinimumDimensions.equals(that.mMinimumDimensions)
+ && mIsTopNonFishingChild == that.mIsTopNonFishingChild;
}
private TaskFragmentInfo(Parcel in) {
@@ -252,6 +266,7 @@
mIsTaskFragmentClearedForPip = in.readBoolean();
mIsClearedForReorderActivityToFront = in.readBoolean();
mMinimumDimensions.readFromParcel(in);
+ mIsTopNonFishingChild = in.readBoolean();
}
/** @hide */
@@ -269,6 +284,7 @@
dest.writeBoolean(mIsTaskFragmentClearedForPip);
dest.writeBoolean(mIsClearedForReorderActivityToFront);
mMinimumDimensions.writeToParcel(dest, flags);
+ dest.writeBoolean(mIsTopNonFishingChild);
}
@NonNull
@@ -299,6 +315,7 @@
+ " isTaskFragmentClearedForPip=" + mIsTaskFragmentClearedForPip
+ " mIsClearedForReorderActivityToFront=" + mIsClearedForReorderActivityToFront
+ " minimumDimensions=" + mMinimumDimensions
+ + " isTopNonFinishingChild=" + mIsTopNonFishingChild
+ "}";
}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 4230641..4c18bbf 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -51,13 +51,6 @@
flag {
namespace: "windowing_sdk"
- name: "embedded_activity_back_nav_flag"
- description: "Refines embedded activity back navigation behavior"
- bug: "293642394"
-}
-
-flag {
- namespace: "windowing_sdk"
name: "cover_display_opt_in"
is_exported: true
description: "Properties to allow apps and activities to opt-in to cover display rendering"
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index ab456a8..6258f5c 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -544,6 +544,14 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
+ if (Settings.Secure.getIntForUser(getContentResolver(),
+ Settings.Secure.SECURE_FRP_MODE, 0,
+ getUserId()) == 1) {
+ Log.e(TAG, "Sharing disabled due to active FRP lock.");
+ super.onCreate(savedInstanceState);
+ finish();
+ return;
+ }
final long intentReceivedTime = System.currentTimeMillis();
mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET);
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 3f7ba0a..b51678e 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -125,9 +125,9 @@
void showInputMethodPickerFromClient(in IInputMethodClient client,
int auxiliarySubtypeMode);
- @EnforcePermission("WRITE_SECURE_SETTINGS")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ @EnforcePermission(allOf = {"WRITE_SECURE_SETTINGS", "INTERACT_ACROSS_USERS_FULL"})
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf = {android.Manifest."
+ + "permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})")
void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId);
@EnforcePermission("TEST_INPUT_METHOD")
@@ -143,9 +143,9 @@
* @param displayId The ID of the display where the input method picker dialog should be shown.
* @param userId The ID of the user that triggered the click.
*/
- @EnforcePermission("WRITE_SECURE_SETTINGS")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ @EnforcePermission(allOf = {"WRITE_SECURE_SETTINGS" ,"INTERACT_ACROSS_USERS_FULL"})
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf = {android.Manifest."
+ + "permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})")
oneway void onImeSwitchButtonClickFromSystem(int displayId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
@@ -168,9 +168,9 @@
oneway void reportPerceptibleAsync(in IBinder windowToken, boolean perceptible);
- @EnforcePermission("INTERNAL_SYSTEM_WINDOW")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
+ @EnforcePermission(allOf = {"INTERNAL_SYSTEM_WINDOW", "INTERACT_ACROSS_USERS_FULL"})
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf = {android.Manifest."
+ + "permission.INTERNAL_SYSTEM_WINDOW, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})")
void removeImeSurface(int displayId);
/** Remove the IME surface. Requires passing the currently focused window. */
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 7c62615..638591f 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2292,7 +2292,7 @@
criteria.mValue.mUsage);
jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass,
gAudioMixMatchCriterionAttrCstor,
- jMixMatchCriterion, criteria.mRule);
+ jAudioAttributes, criteria.mRule);
break;
case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
jAudioAttributes = env->NewObject(gAudioAttributesClass, gAudioAttributesCstor);
@@ -2300,7 +2300,7 @@
criteria.mValue.mSource);
jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass,
gAudioMixMatchCriterionAttrCstor,
- jMixMatchCriterion, criteria.mRule);
+ jAudioAttributes, criteria.mRule);
break;
}
env->CallBooleanMethod(jAudioMixMatchCriterionList, gArrayListMethods.add,
diff --git a/core/res/res/drawable/tooltip_frame.xml b/core/res/res/drawable/tooltip_frame.xml
index 14130c8..e2618ca 100644
--- a/core/res/res/drawable/tooltip_frame.xml
+++ b/core/res/res/drawable/tooltip_frame.xml
@@ -17,5 +17,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/tooltipBackgroundColor" />
- <corners android:radius="@dimen/tooltip_corner_radius" />
-</shape>
\ No newline at end of file
+ <corners android:radius="?attr/tooltipCornerRadius" />
+</shape>
diff --git a/core/res/res/layout/tooltip.xml b/core/res/res/layout/tooltip.xml
index 376c5eb..5b6799e 100644
--- a/core/res/res/layout/tooltip.xml
+++ b/core/res/res/layout/tooltip.xml
@@ -27,10 +27,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/tooltip_margin"
- android:paddingStart="@dimen/tooltip_horizontal_padding"
- android:paddingEnd="@dimen/tooltip_horizontal_padding"
- android:paddingTop="@dimen/tooltip_vertical_padding"
- android:paddingBottom="@dimen/tooltip_vertical_padding"
+ android:paddingStart="?attr/tooltipHorizontalPadding"
+ android:paddingEnd="?attr/tooltipHorizontalPadding"
+ android:paddingTop="?attr/tooltipVerticalPadding"
+ android:paddingBottom="?attr/tooltipVerticalPadding"
android:maxWidth="256dp"
android:background="?android:attr/tooltipFrameBackground"
android:textAppearance="@style/TextAppearance.Tooltip"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 7cc9e13..440219d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1078,6 +1078,11 @@
<!-- Background color to use for tooltip popups. -->
<attr name="tooltipBackgroundColor" format="reference|color" />
+ <attr name="tooltipCornerRadius" format="dimension" />
+ <attr name="tooltipHorizontalPadding" format="dimension" />
+ <attr name="tooltipVerticalPadding" format="dimension" />
+ <attr name="tooltipFontSize" format="dimension" />
+
<!-- Theme to use for Search Dialogs. -->
<attr name="searchDialogTheme" format="reference" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 6cba84b..77b5587 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -771,6 +771,7 @@
<dimen name="tooltip_precise_anchor_threshold">96dp</dimen>
<!-- Extra tooltip offset used when anchoring to the mouse/touch position -->
<dimen name="tooltip_precise_anchor_extra_offset">8dp</dimen>
+ <dimen name="tooltip_font_size">14sp</dimen>
<!-- The max amount of scroll ItemTouchHelper will trigger if dragged view is out of
RecyclerView's bounds.-->
diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml
index 972fe7e..35f35fb 100644
--- a/core/res/res/values/dimens_material.xml
+++ b/core/res/res/values/dimens_material.xml
@@ -204,4 +204,9 @@
<dimen name="progress_bar_size_small">16dip</dimen>
<dimen name="progress_bar_size_medium">48dp</dimen>
<dimen name="progress_bar_size_large">76dp</dimen>
+
+ <dimen name="tooltip_corner_radius_material">4dp</dimen>
+ <dimen name="tooltip_horizontal_padding_material">8dp</dimen>
+ <dimen name="tooltip_vertical_padding_material">4dp</dimen>
+ <dimen name="tooltip_font_size_material">12sp</dimen>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ec865f6..e94db2d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6555,4 +6555,7 @@
<string name="keyboard_shortcut_group_applications_maps">Maps</string>
<!-- User visible title for the keyboard shortcut group containing system-wide application launch shortcuts. [CHAR-LIMIT=70] -->
<string name="keyboard_shortcut_group_applications">Applications</string>
+
+ <!-- Fingerprint loe notification string -->
+ <string name="fingerprint_loe_notification_msg">Your fingerprints can no longer be recognized. Set up Fingerprint Unlock again.</string>
</resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index aabc8ca..c084b4c 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -998,7 +998,7 @@
<style name="TextAppearance.Tooltip">
<item name="fontFamily">sans-serif</item>
- <item name="textSize">14sp</item>
+ <item name="textSize">?android:attr/tooltipFontSize</item>
</style>
<style name="Widget.ActivityChooserView">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6b58396..cbf3fe7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5584,4 +5584,7 @@
<java-symbol type="string" name="keyboard_shortcut_group_applications_music" />
<java-symbol type="string" name="keyboard_shortcut_group_applications_sms" />
<java-symbol type="string" name="keyboard_shortcut_group_applications" />
+
+ <!-- Fingerprint loe notification string -->
+ <java-symbol type="string" name="fingerprint_loe_notification_msg" />
</resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index c3d304d..3b3bb8d 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -461,6 +461,10 @@
<item name="tooltipFrameBackground">@drawable/tooltip_frame</item>
<item name="tooltipForegroundColor">@color/bright_foreground_light</item>
<item name="tooltipBackgroundColor">@color/tooltip_background_light</item>
+ <item name="tooltipCornerRadius">@dimen/tooltip_corner_radius</item>
+ <item name="tooltipHorizontalPadding">@dimen/tooltip_horizontal_padding</item>
+ <item name="tooltipVerticalPadding">@dimen/tooltip_vertical_padding</item>
+ <item name="tooltipFontSize">@dimen/tooltip_font_size</item>
<!-- Autofill: max width/height of the dataset picker as a fraction of screen size -->
<item name="autofillDatasetPickerMaxWidth">@dimen/autofill_dataset_picker_max_width</item>
@@ -582,9 +586,10 @@
<item name="floatingToolbarOpenDrawable">@drawable/ic_menu_moreoverflow_material_light</item>
<item name="floatingToolbarDividerColor">@color/floating_popup_divider_light</item>
- <!-- Tooltip popup colors -->
+ <!-- Tooltip popup styles -->
<item name="tooltipForegroundColor">@color/bright_foreground_dark</item>
<item name="tooltipBackgroundColor">@color/tooltip_background_dark</item>
+
</style>
<!-- Variant of {@link #Theme_Light} with no title bar -->
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 8e2fb34..9f11208 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -408,8 +408,12 @@
<item name="colorProgressBackgroundNormal">?attr/colorControlNormal</item>
<!-- Tooltip popup properties -->
- <item name="tooltipForegroundColor">@color/foreground_material_light</item>
- <item name="tooltipBackgroundColor">@color/tooltip_background_light</item>
+ <item name="tooltipForegroundColor">@color/system_on_surface_light</item>
+ <item name="tooltipBackgroundColor">@color/system_surface_light</item>
+ <item name="tooltipCornerRadius">@dimen/tooltip_corner_radius_material</item>
+ <item name="tooltipHorizontalPadding">@dimen/tooltip_horizontal_padding_material</item>
+ <item name="tooltipVerticalPadding">@dimen/tooltip_vertical_padding_material</item>
+ <item name="tooltipFontSize">@dimen/tooltip_font_size_material</item>
</style>
<!-- Material theme (light version). -->
@@ -785,8 +789,13 @@
<item name="colorProgressBackgroundNormal">?attr/colorControlNormal</item>
<!-- Tooltip popup properties -->
- <item name="tooltipForegroundColor">@color/foreground_material_dark</item>
- <item name="tooltipBackgroundColor">@color/tooltip_background_dark</item>
+ <item name="tooltipForegroundColor">@color/system_on_surface_dark</item>
+ <item name="tooltipBackgroundColor">@color/system_surface_dark</item>
+ <item name="tooltipCornerRadius">@dimen/tooltip_corner_radius_material</item>
+ <item name="tooltipHorizontalPadding">@dimen/tooltip_horizontal_padding_material</item>
+ <item name="tooltipVerticalPadding">@dimen/tooltip_vertical_padding_material</item>
+ <item name="tooltipFontSize">@dimen/tooltip_font_size_material</item>
+
</style>
<!-- Variant of the material (light) theme that has a solid (opaque) action bar
diff --git a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
index a102b3e..eb463fd 100644
--- a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
+++ b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
@@ -30,9 +30,9 @@
import android.view.Choreographer;
import android.view.animation.LinearInterpolator;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/companion/virtual/audio/VirtualAudioSessionTest.java b/core/tests/coretests/src/android/companion/virtual/audio/VirtualAudioSessionTest.java
index e025fae..b91263e 100644
--- a/core/tests/coretests/src/android/companion/virtual/audio/VirtualAudioSessionTest.java
+++ b/core/tests/coretests/src/android/companion/virtual/audio/VirtualAudioSessionTest.java
@@ -35,7 +35,7 @@
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/debug/AdbNotificationsTest.java b/core/tests/coretests/src/android/debug/AdbNotificationsTest.java
index 3496e2c..10eeb35 100644
--- a/core/tests/coretests/src/android/debug/AdbNotificationsTest.java
+++ b/core/tests/coretests/src/android/debug/AdbNotificationsTest.java
@@ -25,8 +25,8 @@
import android.text.TextUtils;
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/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java
index 5f96c17..52f53dd 100644
--- a/core/tests/coretests/src/android/graphics/FontListParserTest.java
+++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java
@@ -16,16 +16,16 @@
package android.graphics;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
import static android.graphics.fonts.FontStyle.FONT_SLANT_ITALIC;
import static android.graphics.fonts.FontStyle.FONT_SLANT_UPRIGHT;
import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
import static android.text.FontConfig.FontFamily.VARIANT_COMPACT;
import static android.text.FontConfig.FontFamily.VARIANT_DEFAULT;
import static android.text.FontConfig.FontFamily.VARIANT_ELEGANT;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
-import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
import static com.google.common.truth.Truth.assertThat;
@@ -38,8 +38,8 @@
import android.text.FontConfig;
import android.util.Xml;
+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/graphics/RectTest.java b/core/tests/coretests/src/android/graphics/RectTest.java
index 2918f44..d0cb5d5 100644
--- a/core/tests/coretests/src/android/graphics/RectTest.java
+++ b/core/tests/coretests/src/android/graphics/RectTest.java
@@ -24,8 +24,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/graphics/TypefaceEqualsTest.java b/core/tests/coretests/src/android/graphics/TypefaceEqualsTest.java
index 6ae7eb7..a94f412 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceEqualsTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceEqualsTest.java
@@ -23,8 +23,8 @@
import android.graphics.fonts.Font;
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/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 0d687b2..10aed8d 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -39,8 +39,8 @@
import android.util.ArrayMap;
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/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java
index 6bf8f56..80efa51 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java
@@ -30,10 +30,10 @@
import android.util.ArrayMap;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/graphics/drawable/DrawableWrapperTest.java b/core/tests/coretests/src/android/graphics/drawable/DrawableWrapperTest.java
index d0a6ff9..4991cd0 100644
--- a/core/tests/coretests/src/android/graphics/drawable/DrawableWrapperTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/DrawableWrapperTest.java
@@ -25,8 +25,8 @@
import android.graphics.PorterDuffXfermode;
import android.graphics.Xfermode;
+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/hardware/input/InputFlagsTest.java b/core/tests/coretests/src/android/hardware/input/InputFlagsTest.java
index 5aeab42..b4f1dee 100644
--- a/core/tests/coretests/src/android/hardware/input/InputFlagsTest.java
+++ b/core/tests/coretests/src/android/hardware/input/InputFlagsTest.java
@@ -21,8 +21,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/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java
index b13bcd1..444ed51 100644
--- a/core/tests/coretests/src/android/net/NetworkKeyTest.java
+++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java
@@ -25,7 +25,7 @@
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
index 3e45a79..46f22ce 100644
--- a/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
+++ b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
@@ -26,7 +26,7 @@
import android.Manifest.permission;
import android.content.Context;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/net/SSLCertificateSocketFactoryTest.java b/core/tests/coretests/src/android/net/SSLCertificateSocketFactoryTest.java
index bc12e72..7413ede 100644
--- a/core/tests/coretests/src/android/net/SSLCertificateSocketFactoryTest.java
+++ b/core/tests/coretests/src/android/net/SSLCertificateSocketFactoryTest.java
@@ -19,7 +19,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-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/net/ScoredNetworkTest.java b/core/tests/coretests/src/android/net/ScoredNetworkTest.java
index d984d86..63eeaa1 100644
--- a/core/tests/coretests/src/android/net/ScoredNetworkTest.java
+++ b/core/tests/coretests/src/android/net/ScoredNetworkTest.java
@@ -26,7 +26,7 @@
import android.os.Bundle;
import android.os.Parcel;
-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/net/SntpClientTest.java b/core/tests/coretests/src/android/net/SntpClientTest.java
index 267fc2b..024d614 100644
--- a/core/tests/coretests/src/android/net/SntpClientTest.java
+++ b/core/tests/coretests/src/android/net/SntpClientTest.java
@@ -29,7 +29,7 @@
import android.platform.test.annotations.Presubmit;
import android.util.Log;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import libcore.util.HexEncoding;
diff --git a/core/tests/coretests/src/android/net/sntp/Duration64Test.java b/core/tests/coretests/src/android/net/sntp/Duration64Test.java
index b228596..b177e18 100644
--- a/core/tests/coretests/src/android/net/sntp/Duration64Test.java
+++ b/core/tests/coretests/src/android/net/sntp/Duration64Test.java
@@ -23,7 +23,7 @@
import android.platform.test.annotations.Presubmit;
-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/net/sntp/Timestamp64Test.java b/core/tests/coretests/src/android/net/sntp/Timestamp64Test.java
index 200c80e..9f95132 100644
--- a/core/tests/coretests/src/android/net/sntp/Timestamp64Test.java
+++ b/core/tests/coretests/src/android/net/sntp/Timestamp64Test.java
@@ -23,7 +23,7 @@
import android.platform.test.annotations.Presubmit;
-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/preference/PreferenceIconSpaceTest.java b/core/tests/coretests/src/android/preference/PreferenceIconSpaceTest.java
index 0deb77e..55a347e 100644
--- a/core/tests/coretests/src/android/preference/PreferenceIconSpaceTest.java
+++ b/core/tests/coretests/src/android/preference/PreferenceIconSpaceTest.java
@@ -27,8 +27,8 @@
import android.widget.ImageView;
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/print/IPrintManagerParametersTest.java b/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
index c25aa51..746c8ca 100644
--- a/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
+++ b/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
@@ -42,9 +42,9 @@
import android.print.test.services.StubbablePrinterDiscoverySession;
import android.printservice.recommendation.IRecommendationsChangeListener;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigServiceManagerTest.java b/core/tests/coretests/src/android/provider/DeviceConfigServiceManagerTest.java
index e20258a..a60746f 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigServiceManagerTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigServiceManagerTest.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.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 9300d1e..681396e 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -29,9 +29,9 @@
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Assert;
diff --git a/core/tests/coretests/src/android/provider/FontsContractE2ETest.java b/core/tests/coretests/src/android/provider/FontsContractE2ETest.java
index 7e02be8..4010171 100644
--- a/core/tests/coretests/src/android/provider/FontsContractE2ETest.java
+++ b/core/tests/coretests/src/android/provider/FontsContractE2ETest.java
@@ -33,8 +33,8 @@
import android.provider.FontsContract.FontFamilyResult;
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/service/controls/ControlProviderServiceTest.java b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java
index 4d446901..6eaf2e4 100644
--- a/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java
+++ b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java
@@ -45,9 +45,9 @@
import android.service.controls.actions.ControlActionWrapper;
import android.service.controls.templates.ThumbnailTemplate;
+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.internal.R;
diff --git a/core/tests/coretests/src/android/service/controls/actions/ControlActionTest.java b/core/tests/coretests/src/android/service/controls/actions/ControlActionTest.java
index d8088b7..44bdc53 100644
--- a/core/tests/coretests/src/android/service/controls/actions/ControlActionTest.java
+++ b/core/tests/coretests/src/android/service/controls/actions/ControlActionTest.java
@@ -23,8 +23,8 @@
import android.os.Parcel;
+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/service/controls/templates/ControlTemplateTest.java b/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java
index 91a3ba7..73b6f648 100644
--- a/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java
+++ b/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java
@@ -25,8 +25,8 @@
import android.graphics.drawable.Icon;
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java b/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java
index 6792d0b..f4206c8 100644
--- a/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java
+++ b/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java
@@ -26,8 +26,8 @@
import android.service.carrier.CarrierIdentifier;
import android.telephony.UiccAccessRule;
+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/service/notification/NotificationListenerFilterTest.java b/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
index a121941..44456e9 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
@@ -27,8 +27,8 @@
import android.os.Parcel;
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/service/notification/StatusBarNotificationTest.java b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
index 76c9f88..5042408 100644
--- a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
+++ b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
@@ -37,8 +37,8 @@
import android.os.UserHandle;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
diff --git a/core/tests/coretests/src/android/service/quicksettings/TileTest.java b/core/tests/coretests/src/android/service/quicksettings/TileTest.java
index ca6c3b4..43f9122 100644
--- a/core/tests/coretests/src/android/service/quicksettings/TileTest.java
+++ b/core/tests/coretests/src/android/service/quicksettings/TileTest.java
@@ -18,8 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+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/service/settings/suggestions/SuggestionServiceTest.java b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java
index 64edda5..85659d6 100644
--- a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java
+++ b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java
@@ -23,9 +23,9 @@
import android.os.RemoteException;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.rule.ServiceTestRule;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
index e0eb197..03096de 100644
--- a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
+++ b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
@@ -26,8 +26,8 @@
import android.os.Parcel;
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/telephony/PinResultTest.java b/core/tests/coretests/src/android/telephony/PinResultTest.java
index c260807..f5432ee 100644
--- a/core/tests/coretests/src/android/telephony/PinResultTest.java
+++ b/core/tests/coretests/src/android/telephony/PinResultTest.java
@@ -18,7 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
-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/tracing/perfetto/DataSourceTest.java b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
index df9a89e..bbeb18d 100644
--- a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
+++ b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
@@ -37,7 +37,7 @@
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.truth.Truth;
import com.google.protobuf.InvalidProtocolBufferException;
diff --git a/core/tests/coretests/src/android/transition/AutoTransitionTest.java b/core/tests/coretests/src/android/transition/AutoTransitionTest.java
index deae967..5d58fead 100644
--- a/core/tests/coretests/src/android/transition/AutoTransitionTest.java
+++ b/core/tests/coretests/src/android/transition/AutoTransitionTest.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+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/com/android/internal/infra/AndroidFutureTest.java b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
index 3a27225..178e93a 100644
--- a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
+++ b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
@@ -22,7 +22,7 @@
import android.os.Parcel;
-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/com/android/internal/infra/ServiceConnectorTest.java b/core/tests/coretests/src/com/android/internal/infra/ServiceConnectorTest.java
index 725dcf3..3d1b565 100644
--- a/core/tests/coretests/src/com/android/internal/infra/ServiceConnectorTest.java
+++ b/core/tests/coretests/src/com/android/internal/infra/ServiceConnectorTest.java
@@ -29,8 +29,8 @@
import android.os.UserHandle;
import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.aidl.ITestServiceConnectorService;
import com.android.internal.infra.ServiceConnectorTest.CapturingServiceLifecycleCallbacks.ServiceLifeCycleEvent;
diff --git a/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
index 7054cc0..b86cb4a 100644
--- a/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
@@ -20,8 +20,8 @@
import android.metrics.LogMaker;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.testing.FakeMetricsLogger;
diff --git a/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
index 7840f71..fc28627 100644
--- a/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
@@ -18,8 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.logging.testing.UiEventLoggerFake;
diff --git a/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java b/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java
index d1ef61b..d1c0668 100644
--- a/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java
+++ b/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java
@@ -19,7 +19,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 8e1fde0..409cde3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -119,7 +119,8 @@
// TODO(b/243518738): Move to WM Extensions if we have requirement of overlay without
// association. It's not set in WM Extensions nor Wm Jetpack library currently.
- private static final String KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY =
+ @VisibleForTesting
+ static final String KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY =
"androidx.window.extensions.embedding.shouldAssociateWithLaunchingActivity";
@VisibleForTesting
@@ -2742,89 +2743,70 @@
}
final int taskId = getTaskId(launchActivity);
- if (!overlayContainers.isEmpty()) {
- for (final TaskFragmentContainer overlayContainer : overlayContainers) {
- final boolean isTopNonFinishingOverlay = overlayContainer.equals(
- overlayContainer.getTaskContainer().getTopNonFinishingTaskFragmentContainer(
- true /* includePin */, true /* includeOverlay */));
- if (taskId != overlayContainer.getTaskId()) {
- // If there's an overlay container with same tag in a different task,
- // dismiss the overlay container since the tag must be unique per process.
- if (overlayTag.equals(overlayContainer.getOverlayTag())) {
- Log.w(TAG, "The overlay container with tag:"
- + overlayContainer.getOverlayTag() + " is dismissed because"
- + " there's an existing overlay container with the same tag but"
- + " different task ID:" + overlayContainer.getTaskId() + ". "
- + "The new associated activity is " + launchActivity);
- mPresenter.cleanupContainer(wct, overlayContainer,
- false /* shouldFinishDependant */);
- }
- continue;
- }
- if (!overlayTag.equals(overlayContainer.getOverlayTag())) {
- // If there's an overlay container with different tag on top in the same
- // task, dismiss the existing overlay container.
- if (isTopNonFinishingOverlay) {
- mPresenter.cleanupContainer(wct, overlayContainer,
- false /* shouldFinishDependant */);
- }
- continue;
- }
- // The overlay container has the same tag and task ID with the new launching
- // overlay container.
- if (!isTopNonFinishingOverlay) {
- // Dismiss the invisible overlay container regardless of activity
- // association if it collides the tag of new launched overlay container .
- Log.w(TAG, "The invisible overlay container with tag:"
- + overlayContainer.getOverlayTag() + " is dismissed because"
- + " there's a launching overlay container with the same tag."
- + " The new associated activity is " + launchActivity);
- mPresenter.cleanupContainer(wct, overlayContainer,
- false /* shouldFinishDependant */);
- continue;
- }
- // Requesting an always-on-top overlay.
- if (!associateLaunchingActivity) {
- if (overlayContainer.isOverlayWithActivityAssociation()) {
- // Dismiss the overlay container since it has associated with an activity.
- Log.w(TAG, "The overlay container with tag:"
- + overlayContainer.getOverlayTag() + " is dismissed because"
- + " there's an existing overlay container with the same tag but"
- + " different associated launching activity. The overlay container"
- + " doesn't associate with any activity.");
- mPresenter.cleanupContainer(wct, overlayContainer,
- false /* shouldFinishDependant */);
- continue;
- } else {
- // The existing overlay container doesn't associate an activity as well.
- // Just update the overlay and return.
- // Note that going to this condition means the tag, task ID matches a
- // visible always-on-top overlay, and won't dismiss any overlay any more.
- mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
- getMinDimensions(intent));
- return overlayContainer;
- }
- }
- if (launchActivity.getActivityToken()
- != overlayContainer.getAssociatedActivityToken()) {
- Log.w(TAG, "The overlay container with tag:"
- + overlayContainer.getOverlayTag() + " is dismissed because"
- + " there's an existing overlay container with the same tag but"
- + " different associated launching activity. The new associated"
- + " activity is " + launchActivity);
- // The associated activity must be the same, or it will be dismissed.
- mPresenter.cleanupContainer(wct, overlayContainer,
- false /* shouldFinishDependant */);
- continue;
- }
- // Reaching here means the launching activity launch an overlay container with the
- // same task ID, tag, while there's a previously launching visible overlay
- // container. We'll regard it as updating the existing overlay container.
+ // Overlay container policy:
+ // 1. Overlay tag must be unique per process.
+ // a. For associated overlay, if a new launched overlay container has the same tag as
+ // an existing one, the existing overlay will be dismissed regardless of its task
+ // and window hierarchy.
+ // b. For always-on-top overlay, if there's an overlay container has the same tag in the
+ // launched task, the overlay container will be re-used, which means the
+ // ActivityStackAttributes will be applied and the launched activity will be positioned
+ // on top of the overlay container.
+ // 2. There must be at most one overlay that partially occludes a visible activity per task.
+ // a. For associated overlay, only the top visible overlay container in the launched task
+ // will be dismissed.
+ // b. Always-on-top overlay is always visible. If there's an overlay with different tags
+ // in the same task, the overlay will be dismissed in case an activity above
+ // the overlay is dismissed and the overlay is shown unexpectedly.
+ for (final TaskFragmentContainer overlayContainer : overlayContainers) {
+ final boolean isTopNonFinishingOverlay = overlayContainer.isTopNonFinishingChild();
+ final boolean areInSameTask = taskId == overlayContainer.getTaskId();
+ final boolean haveSameTag = overlayTag.equals(overlayContainer.getOverlayTag());
+ if (!associateLaunchingActivity && overlayContainer.isAlwaysOnTopOverlay()
+ && haveSameTag && areInSameTask) {
+ // Just launch the activity and update the existing always-on-top overlay
+ // if the requested overlay is an always-on-top overlay with the same tag
+ // as the existing one.
mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
getMinDimensions(intent));
return overlayContainer;
-
}
+ if (haveSameTag) {
+ // For other tag match, we should clean up the existing overlay since the overlay
+ // tag must be unique per process.
+ Log.w(TAG, "The overlay container with tag:"
+ + overlayContainer.getOverlayTag() + " is dismissed with "
+ + " the launching activity=" + launchActivity
+ + " because there's an existing overlay container with the same tag.");
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
+ }
+ if (!areInSameTask) {
+ // Early return here because we won't clean-up or update overlay from different
+ // tasks except tag collision.
+ continue;
+ }
+ if (associateLaunchingActivity) {
+ // For associated overlay, we only dismiss the overlay if it's the top non-finishing
+ // child of its parent container.
+ if (isTopNonFinishingOverlay) {
+ Log.w(TAG, "The on-top overlay container with tag:"
+ + overlayContainer.getOverlayTag() + " is dismissed with "
+ + " the launching activity=" + launchActivity
+ + "because we only allow one overlay on top.");
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
+ }
+ continue;
+ }
+ // Otherwise, we should clean up the overlay in the task because we only allow one
+ // overlay when an always-on-top overlay is launched.
+ Log.w(TAG, "The overlay container with tag:"
+ + overlayContainer.getOverlayTag() + " is dismissed with "
+ + " the launching activity=" + launchActivity
+ + "because an always-on-top overlay is launched.");
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
}
// Launch the overlay container to the task with taskId.
return createEmptyContainer(wct, intent, taskId, attrs, launchActivity, overlayTag,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 7173b0c..d0e2c99 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -340,6 +340,13 @@
return mInfo != null && mInfo.isVisible();
}
+ /**
+ * See {@link TaskFragmentInfo#isTopNonFinishingChild()}
+ */
+ boolean isTopNonFinishingChild() {
+ return mInfo != null && mInfo.isTopNonFinishingChild();
+ }
+
/** Whether the TaskFragment is in an intermediate state waiting for the server update.*/
boolean isInIntermediateState() {
if (mInfo == null) {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index d649c6d..7dc78fd 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -163,12 +163,14 @@
}
/** Creates a mock TaskFragmentInfo for the given TaskFragment. */
+ @NonNull
static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
@NonNull Activity activity) {
return createMockTaskFragmentInfo(container, activity, true /* isVisible */);
}
/** Creates a mock TaskFragmentInfo for the given TaskFragment. */
+ @NonNull
static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
@NonNull Activity activity, boolean isVisible) {
return new TaskFragmentInfo(container.getTaskFragmentToken(),
@@ -182,7 +184,27 @@
false /* isTaskClearedForReuse */,
false /* isTaskFragmentClearedForPip */,
false /* isClearedForReorderActivityToFront */,
- new Point());
+ new Point(),
+ false /* isTopChild */);
+ }
+
+ /** Creates a mock TaskFragmentInfo for the given TaskFragment. */
+ @NonNull
+ static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
+ @NonNull Activity activity, boolean isVisible, boolean isOnTop) {
+ return new TaskFragmentInfo(container.getTaskFragmentToken(),
+ mock(WindowContainerToken.class),
+ new Configuration(),
+ 1,
+ isVisible,
+ Collections.singletonList(activity.getActivityToken()),
+ new ArrayList<>(),
+ new Point(),
+ false /* isTaskClearedForReuse */,
+ false /* isTaskFragmentClearedForPip */,
+ false /* isClearedForReorderActivityToFront */,
+ new Point(),
+ isOnTop);
}
static ActivityInfo createActivityInfoWithMinDimensions() {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index ad41b18..8911d18b 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -114,6 +114,7 @@
mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */,
false /* isVisible */, new ArrayList<>(), new ArrayList<>(), new Point(),
false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */,
- false /* isClearedForReorderActivityToFront */, new Point());
+ false /* isClearedForReorderActivityToFront */, new Point(),
+ false /* isTopChild */);
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 1c4c887..475475b 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -30,6 +30,7 @@
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTfContainer;
+import static androidx.window.extensions.embedding.SplitController.KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT;
@@ -94,6 +95,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -267,7 +269,7 @@
}
@Test
- public void testCreateOrUpdateOverlay_visibleOverlaySameTagInTask_dismissOverlay() {
+ public void testCreateOrUpdateOverlay_topOverlayInTask_dismissOverlay() {
createExistingOverlayContainers();
final TaskFragmentContainer overlayContainer =
@@ -295,26 +297,6 @@
}
@Test
- public void testCreateOrUpdateOverlay_sameTagTaskAndActivity_updateOverlay() {
- createExistingOverlayContainers();
-
- final Rect bounds = new Rect(0, 0, 100, 100);
- mSplitController.setActivityStackAttributesCalculator(params ->
- new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- mOverlayContainer1.getOverlayTag());
-
- assertWithMessage("overlayContainer1 must be updated since the new overlay container"
- + " is launched with the same tag and task")
- .that(mSplitController.getAllNonFinishingOverlayContainers())
- .containsExactly(mOverlayContainer1, mOverlayContainer2);
-
- assertThat(overlayContainer).isEqualTo(mOverlayContainer1);
- verify(mSplitPresenter).resizeTaskFragment(eq(mTransaction),
- eq(mOverlayContainer1.getTaskFragmentToken()), eq(bounds));
- }
-
- @Test
public void testCreateOrUpdateOverlay_sameTagAndTaskButNotActivity_dismissOverlay() {
createExistingOverlayContainers();
@@ -362,6 +344,43 @@
}
@Test
+ public void testCreateOrUpdateAlwaysOnTopOverlay_dismissMultipleOverlaysInTask() {
+ createExistingOverlayContainers();
+ // Create another overlay in task.
+ final TaskFragmentContainer overlayContainer3 =
+ createTestOverlayContainer(TASK_ID, "test3");
+ assertThat(mSplitController.getAllNonFinishingOverlayContainers())
+ .containsExactly(mOverlayContainer1, mOverlayContainer2, overlayContainer3);
+
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateAlwaysOnTopOverlay("test4");
+
+ assertWithMessage("overlayContainer1 and overlayContainer3 must be dismissed")
+ .that(mSplitController.getAllNonFinishingOverlayContainers())
+ .containsExactly(mOverlayContainer2, overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateAlwaysOnTopOverlay_updateOverlay() {
+ createExistingOverlayContainers();
+ // Create another overlay in task.
+ final TaskFragmentContainer alwaysOnTopOverlay = createTestOverlayContainer(TASK_ID,
+ "test3", true /* isVisible */, false /* associateLaunchingActivity */);
+ final ActivityStackAttributes attrs = new ActivityStackAttributes.Builder()
+ .setRelativeBounds(new Rect(0, 0, 100, 100)).build();
+ mSplitController.setActivityStackAttributesCalculator(params -> attrs);
+
+ Mockito.clearInvocations(mSplitPresenter);
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateAlwaysOnTopOverlay(alwaysOnTopOverlay.getOverlayTag());
+
+ assertWithMessage("overlayContainer1 and overlayContainer3 must be dismissed")
+ .that(mSplitController.getAllNonFinishingOverlayContainers())
+ .containsExactly(mOverlayContainer2, alwaysOnTopOverlay);
+ assertThat(overlayContainer).isEqualTo(alwaysOnTopOverlay);
+ }
+
+ @Test
public void testCreateOrUpdateOverlay_launchFromSplit_returnNull() {
final Activity primaryActivity = createMockActivity();
final Activity secondaryActivity = createMockActivity();
@@ -381,13 +400,13 @@
}
private void createExistingOverlayContainers() {
- createExistingOverlayContainers(true /* visible */);
+ createExistingOverlayContainers(true /* isOnTop */);
}
- private void createExistingOverlayContainers(boolean visible) {
- mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1", visible,
+ private void createExistingOverlayContainers(boolean isOnTop) {
+ mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1", isOnTop,
true /* associatedLaunchingActivity */, mActivity);
- mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2", visible);
+ mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2", isOnTop);
List<TaskFragmentContainer> overlayContainers = mSplitController
.getAllNonFinishingOverlayContainers();
assertThat(overlayContainers).containsExactly(mOverlayContainer1, mOverlayContainer2);
@@ -966,6 +985,16 @@
launchOptions, mIntent, activity);
}
+ @Nullable
+ private TaskFragmentContainer createOrUpdateAlwaysOnTopOverlay(
+ @NonNull String tag) {
+ final Bundle launchOptions = new Bundle();
+ launchOptions.putBoolean(KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY, false);
+ launchOptions.putString(KEY_OVERLAY_TAG, tag);
+ return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction,
+ launchOptions, mIntent, createMockActivity());
+ }
+
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
@NonNull
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
@@ -975,10 +1004,10 @@
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
@NonNull
private TaskFragmentContainer createMockTaskFragmentContainer(
- @NonNull Activity activity, boolean isVisible) {
+ @NonNull Activity activity, boolean isOnTop) {
final TaskFragmentContainer container = createTfContainer(mSplitController,
activity.getTaskId(), activity);
- setupTaskFragmentInfo(container, activity, isVisible);
+ setupTaskFragmentInfo(container, activity, isOnTop);
return container;
}
@@ -990,8 +1019,8 @@
@NonNull
private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag,
- boolean isVisible) {
- return createTestOverlayContainer(taskId, tag, isVisible,
+ boolean isOnTop) {
+ return createTestOverlayContainer(taskId, tag, isOnTop,
true /* associateLaunchingActivity */);
}
@@ -1002,11 +1031,9 @@
null /* launchingActivity */);
}
- // TODO(b/243518738): add more test coverage on overlay container without activity association
- // once we have use cases.
@NonNull
private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag,
- boolean isVisible, boolean associateLaunchingActivity,
+ boolean isOnTop, boolean associateLaunchingActivity,
@Nullable Activity launchingActivity) {
final Activity activity = launchingActivity != null
? launchingActivity : createMockActivity();
@@ -1017,14 +1044,15 @@
.setLaunchOptions(Bundle.EMPTY)
.setAssociatedActivity(associateLaunchingActivity ? activity : null)
.build();
- setupTaskFragmentInfo(overlayContainer, createMockActivity(), isVisible);
+ setupTaskFragmentInfo(overlayContainer, createMockActivity(), isOnTop);
return overlayContainer;
}
private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
@NonNull Activity activity,
- boolean isVisible) {
- final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity, isVisible);
+ boolean isOnTop) {
+ final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity, isOnTop,
+ isOnTop);
container.setInfo(mTransaction, info);
mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
}
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 269a586..606ebb4 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -554,15 +554,10 @@
enable_windowing_edge_drag_resize is disabled. -->
<dimen name="freeform_resize_corner">44dp</dimen>
- <!-- The width of the area at the sides of the screen where a freeform task will transition to
- split select if dragged until the touch input is within the range. -->
- <dimen name="desktop_mode_transition_area_width">32dp</dimen>
+ <!-- The thickness in dp for all desktop drag transition regions. -->
+ <dimen name="desktop_mode_transition_region_thickness">44dp</dimen>
- <!-- The width of the area where a desktop task will transition to fullscreen. -->
- <dimen name="desktop_mode_fullscreen_from_desktop_width">80dp</dimen>
-
- <!-- The height of the area where a desktop task will transition to fullscreen. -->
- <dimen name="desktop_mode_fullscreen_from_desktop_height">40dp</dimen>
+ <item type="dimen" format="float" name="desktop_mode_fullscreen_region_scale">0.4</item>
<!-- The height on the screen where drag to the left or right edge will result in a
desktop task snapping to split size. The empty space between this and the top is to allow
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index 2b01eac..1304969 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -43,7 +43,8 @@
APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true),
TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true),
- DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true);
+ DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true),
+ ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true);
/**
* Determines state of flag based on the actual flag and desktop mode developer option overrides.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 35d3876..0262507 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.dagger;
-import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
import android.annotation.Nullable;
import android.app.KeyguardManager;
@@ -569,7 +569,7 @@
ShellTaskOrganizer shellTaskOrganizer) {
int maxTaskLimit = DesktopModeStatus.getMaxTaskLimit(context);
if (!DesktopModeStatus.canEnterDesktopMode(context)
- || !DESKTOP_WINDOWING_MODE.isEnabled(context)
+ || !ENABLE_DESKTOP_WINDOWING_TASK_LIMIT.isEnabled(context)
|| maxTaskLimit <= 0) {
return Optional.empty();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index ea7e968..06c1e68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -104,6 +104,7 @@
TaskStackListenerImpl taskStackListener,
ShellTaskOrganizer shellTaskOrganizer,
PipTransitionState pipTransitionState,
+ PipTouchHandler pipTouchHandler,
@ShellMainThread ShellExecutor mainExecutor) {
if (!PipUtils.isPip2ExperimentEnabled()) {
return Optional.empty();
@@ -112,7 +113,7 @@
context, shellInit, shellCommandHandler, shellController, displayController,
displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
- pipTransitionState, mainExecutor));
+ pipTransitionState, pipTouchHandler, mainExecutor));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 4299841..131c5c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -250,11 +250,17 @@
logD("getVisibleTaskCount=$it")
}
- /** Adds task (or moves if it already exists) to the top of the ordered list. */
+ /**
+ * Adds task (or moves if it already exists) to the top of the ordered list.
+ *
+ * Unminimizes the task if it is minimized.
+ */
fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
logD("Add or move task to top: display=%d taskId=%d", taskId, displayId)
desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
+ // Unminimize the task if it is minimized.
+ unminimizeTask(displayId, taskId)
}
/** Minimizes the task for [taskId] and [displayId] */
@@ -270,13 +276,37 @@
logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)
}
- /** Removes task from the ordered list. */
+ private fun getDisplayIdForTask(taskId: Int): Int? {
+ desktopTaskDataByDisplayId.forEach { displayId, data ->
+ if (taskId in data.freeformTasksInZOrder) {
+ return displayId
+ }
+ }
+ logW("No display id found for task: taskId=%d", taskId)
+ return null
+ }
+
+ /**
+ * Removes [taskId] from the respective display. If [INVALID_DISPLAY], the original display id
+ * will be looked up from the task id.
+ */
fun removeFreeformTask(displayId: Int, taskId: Int) {
logD("Removes freeform task: taskId=%d", taskId)
+ if (displayId == INVALID_DISPLAY) {
+ // Removes the original display id of the task.
+ getDisplayIdForTask(taskId)?.let { removeTaskFromDisplay(it, taskId) }
+ } else {
+ removeTaskFromDisplay(displayId, taskId)
+ }
+ }
+
+ /** Removes given task from a valid [displayId]. */
+ private fun removeTaskFromDisplay(displayId: Int, taskId: Int) {
+ logD("Removes freeform task: taskId=%d, displayId=%d", taskId, displayId)
desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
boundsBeforeMaximizeByTaskId.remove(taskId)
- logD("Remaining freeform tasks: %d",
- desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString() ?: "")
+ logD("Remaining freeform tasks: %s",
+ desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString())
}
/**
@@ -358,3 +388,4 @@
private fun <T> Iterable<T>.toDumpString(): String =
joinToString(separator = ", ", prefix = "[", postfix = "]")
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index ed0d2b8..6011db7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -105,7 +105,7 @@
// If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
IndicatorType result = IndicatorType.NO_INDICATOR;
final int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
+ com.android.wm.shell.R.dimen.desktop_mode_transition_region_thickness);
// Because drags in freeform use task position for indicator calculation, we need to
// account for the possibility of the task going off the top of the screen by captionHeight
final int captionHeight = mContext.getResources().getDimensionPixelSize(
@@ -140,18 +140,19 @@
final Region region = new Region();
int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
? mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height)
+ com.android.wm.shell.R.dimen.desktop_mode_transition_region_thickness)
: 2 * layout.stableInsets().top;
- // A thin, short Rect at the top of the screen.
+ // A Rect at the top of the screen that takes up the center 40%.
if (windowingMode == WINDOWING_MODE_FREEFORM) {
- int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_width);
- region.union(new Rect((layout.width() / 2) - (fromFreeformWidth / 2),
+ final float toFullscreenScale = mContext.getResources().getFloat(
+ R.dimen.desktop_mode_fullscreen_region_scale);
+ final float toFullscreenWidth = (layout.width() * toFullscreenScale);
+ region.union(new Rect((int) ((layout.width() / 2f) - (toFullscreenWidth / 2f)),
-captionHeight,
- (layout.width() / 2) + (fromFreeformWidth / 2),
+ (int) ((layout.width() / 2f) + (toFullscreenWidth / 2f)),
transitionHeight));
}
- // A screen-wide, shorter Rect if the task is in fullscreen or split.
+ // A screen-wide Rect if the task is in fullscreen or split.
if (windowingMode == WINDOWING_MODE_FULLSCREEN
|| windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
region.union(new Rect(0,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index a011ff5..87d1800 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -33,7 +33,8 @@
* Limits the number of tasks shown in Desktop Mode.
*
* This class should only be used if
- * [com.android.window.flags.Flags.enableDesktopWindowingTaskLimit()] is true.
+ * [com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT]
+ * is enabled and [maxTasksLimit] is strictly greater than 0.
*/
class DesktopTasksLimiter (
transitions: Transitions,
@@ -86,10 +87,10 @@
}
/**
- * Returns whether the given Task is being reordered to the back in the given transition, or
- * is already invisible.
+ * Returns whether the Task [taskDetails] is being reordered to the back in the transition
+ * [info], or is already invisible.
*
- * <p> This check can be used to double-check that a task was indeed minimized before
+ * This check can be used to double-check that a task was indeed minimized before
* marking it as such.
*/
private fun isTaskReorderedToBackOrInvisible(
@@ -149,8 +150,8 @@
}
/**
- * Mark a task as minimized, this should only be done after the corresponding transition has
- * finished so we don't minimize the task if the transition fails.
+ * Mark [taskId], which must be on [displayId], as minimized, this should only be done after the
+ * corresponding transition has finished so we don't minimize the task if the transition fails.
*/
private fun markTaskMinimized(displayId: Int, taskId: Int) {
ProtoLog.v(
@@ -161,11 +162,9 @@
/**
* Add a minimize-transition to [wct] if adding [newFrontTaskInfo] brings us over the task
- * limit.
+ * limit, returning the task to minimize.
*
- * @param transition the transition that the minimize-transition will be appended to, or null if
- * the transition will be started later.
- * @return the ID of the minimized task, or null if no task is being minimized.
+ * The task must be on [displayId].
*/
fun addAndGetMinimizeTaskChangesIfNeeded(
displayId: Int,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 229d972..2931ef3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -99,7 +99,6 @@
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
- repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId);
if (taskInfo.isVisible) {
repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId,
@@ -161,7 +160,6 @@
if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused) {
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
- repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId);
});
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 7451d22..284620e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -272,6 +272,7 @@
final boolean changed = onDisplayRotationChanged(mContext, outBounds, currentBounds,
mTmpInsetBounds, displayId, fromRotation, toRotation, t);
if (changed) {
+ mMenuController.hideMenu();
// If the pip was in the offset zone earlier, adjust the new bounds to the bottom of the
// movement bounds
mTouchHandler.adjustBoundsForRotation(outBounds, mPipBoundsState.getBounds(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 8aa0933..94fe286 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -88,6 +88,7 @@
private final TaskStackListenerImpl mTaskStackListener;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final PipTransitionState mPipTransitionState;
+ private final PipTouchHandler mPipTouchHandler;
private final ShellExecutor mMainExecutor;
private final PipImpl mImpl;
private Consumer<Boolean> mOnIsInPipStateChangedListener;
@@ -130,6 +131,7 @@
TaskStackListenerImpl taskStackListener,
ShellTaskOrganizer shellTaskOrganizer,
PipTransitionState pipTransitionState,
+ PipTouchHandler pipTouchHandler,
ShellExecutor mainExecutor) {
mContext = context;
mShellCommandHandler = shellCommandHandler;
@@ -144,6 +146,7 @@
mShellTaskOrganizer = shellTaskOrganizer;
mPipTransitionState = pipTransitionState;
mPipTransitionState.addPipTransitionStateChangedListener(this);
+ mPipTouchHandler = pipTouchHandler;
mMainExecutor = mainExecutor;
mImpl = new PipImpl();
@@ -168,6 +171,7 @@
TaskStackListenerImpl taskStackListener,
ShellTaskOrganizer shellTaskOrganizer,
PipTransitionState pipTransitionState,
+ PipTouchHandler pipTouchHandler,
ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -177,7 +181,7 @@
return new PipController(context, shellInit, shellCommandHandler, shellController,
displayController, displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
- pipTransitionState, mainExecutor);
+ pipTransitionState, pipTouchHandler, mainExecutor);
}
public PipImpl getPipImpl() {
@@ -204,7 +208,9 @@
mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
new ImeListener(mDisplayController, mPipDisplayLayoutState.getDisplayId()) {
@Override
- public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
+ public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+ mPipTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
+ }
});
// Allow other outside processes to bind to PiP controller using the key below.
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 e1e072a..83253c6 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
@@ -134,6 +134,8 @@
private final PhysicsAnimator.SpringConfig mConflictResolutionSpringConfig =
new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_NO_BOUNCY);
+ @Nullable private Runnable mUpdateMovementBoundsRunnable;
+
private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
if (mPipBoundsState.getBounds().equals(newBounds)) {
return;
@@ -141,6 +143,7 @@
mMenuController.updateMenuLayout(newBounds);
mPipBoundsState.setBounds(newBounds);
+ maybeUpdateMovementBounds();
};
/**
@@ -566,11 +569,20 @@
+ " callers=\n%s", TAG, originalBounds, offset,
Debug.getCallers(5, " "));
}
+ if (offset == 0) {
+ return;
+ }
+
cancelPhysicsAnimation();
- /*
- mPipTaskOrganizer.scheduleOffsetPip(originalBounds, offset, SHIFT_DURATION,
- mUpdateBoundsCallback);
- */
+
+ Rect adjustedBounds = new Rect(originalBounds);
+ adjustedBounds.offset(0, offset);
+
+ setAnimatingToBounds(adjustedBounds);
+ Bundle extra = new Bundle();
+ extra.putBoolean(ANIMATING_BOUNDS_CHANGE, true);
+ extra.putInt(ANIMATING_BOUNDS_CHANGE_DURATION, SHIFT_DURATION);
+ mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
}
/**
@@ -585,11 +597,11 @@
/** Set new fling configs whose min/max values respect the given movement bounds. */
private void rebuildFlingConfigs() {
mFlingConfigX = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION,
- mPipBoundsAlgorithm.getMovementBounds(getBounds()).left,
- mPipBoundsAlgorithm.getMovementBounds(getBounds()).right);
+ mPipBoundsState.getMovementBounds().left,
+ mPipBoundsState.getMovementBounds().right);
mFlingConfigY = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION,
- mPipBoundsAlgorithm.getMovementBounds(getBounds()).top,
- mPipBoundsAlgorithm.getMovementBounds(getBounds()).bottom);
+ mPipBoundsState.getMovementBounds().top,
+ mPipBoundsState.getMovementBounds().bottom);
final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
mStashConfigX = new PhysicsAnimator.FlingConfig(
DEFAULT_FRICTION,
@@ -671,6 +683,16 @@
cleanUpHighPerfSessionMaybe();
}
+ void setUpdateMovementBoundsRunnable(Runnable updateMovementBoundsRunnable) {
+ mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
+ }
+
+ private void maybeUpdateMovementBounds() {
+ if (mUpdateMovementBoundsRunnable != null) {
+ mUpdateMovementBoundsRunnable.run();
+ }
+ }
+
/**
* Notifies the floating coordinator that we're moving, and sets the animating to bounds so
* we return these bounds from
@@ -807,8 +829,14 @@
startTx, finishTx, mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
destinationBounds, duration, 0f /* angle */);
animator.setAnimationEndCallback(() -> {
- mPipBoundsState.setBounds(destinationBounds);
- // All motion operations have actually finished, so make bounds cache updates.
+ mUpdateBoundsCallback.accept(destinationBounds);
+
+ // In case an ongoing drag/fling was present before a deterministic resize transition
+ // kicked in, we need to update the update bounds properly before cleaning in-motion
+ // state.
+ mPipBoundsState.getMotionBoundsState().setBoundsInMotion(destinationBounds);
+ settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
+
cleanUpHighPerfSessionMaybe();
// Signal that we are done with resize transition
mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */);
@@ -817,7 +845,7 @@
}
private void settlePipBoundsAfterPhysicsAnimation(boolean animatingAfter) {
- if (!animatingAfter) {
+ if (!animatingAfter && mPipBoundsState.getMotionBoundsState().isInMotion()) {
// The physics animation ended, though we may not necessarily be done animating, such as
// when we're still dragging after moving out of the magnetic target. Only set the final
// bounds state and clear motion bounds completely if the whole animation is over.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index 5b0ca18..d28204a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -146,8 +146,8 @@
mUpdateResizeBoundsCallback = (rect) -> {
mUserResizeBounds.set(rect);
// mMotionHelper.synchronizePinnedStackBounds();
- mUpdateMovementBoundsRunnable.run();
mPipBoundsState.setBounds(rect);
+ mUpdateMovementBoundsRunnable.run();
resetState();
};
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 53b80e8..f387e72 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -199,6 +199,7 @@
mMenuController.addListener(new PipMenuListener());
mGesture = new DefaultPipTouchGesture();
mMotionHelper = pipMotionHelper;
+ mMotionHelper.setUpdateMovementBoundsRunnable(this::updateMovementBounds);
mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
mMotionHelper, mainExecutor);
mTouchState = new PipTouchState(ViewConfiguration.get(context),
@@ -317,6 +318,8 @@
mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
mPipResizeGestureHandler.onActivityUnpinned();
mPipInputConsumer.unregisterInputConsumer();
+ mPipBoundsState.setHasUserMovedPip(false);
+ mPipBoundsState.setHasUserResizedPip(false);
}
void onPinnedStackAnimationEnded(
@@ -346,6 +349,22 @@
void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
mIsImeShowing = imeVisible;
mImeHeight = imeHeight;
+
+ // Cache new movement bounds using the new potential IME height.
+ updateMovementBounds();
+
+ mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
+ int delta = mPipBoundsState.getMovementBounds().bottom
+ - mPipBoundsState.getBounds().top;
+
+ boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip()
+ || mPipBoundsState.hasUserResizedPip());
+ if ((imeVisible && delta < 0) || (!imeVisible && !hasUserInteracted)) {
+ // The policy is to ignore an IME disappearing if user has interacted with PiP.
+ // Otherwise, only offset due to an appearing IME if PiP occludes it.
+ mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta);
+ }
+ });
}
void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
@@ -1077,6 +1096,7 @@
switch (newState) {
case PipTransitionState.ENTERED_PIP:
onActivityPinned();
+ updateMovementBounds();
mTouchState.setAllowInputEvents(true);
mTouchState.reset();
break;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 29272be..a132796f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -149,6 +149,12 @@
@Nullable
private SurfaceControl mSwipePipToHomeOverlay;
+ //
+ // Scheduling-related state
+ //
+ @Nullable
+ private Runnable mOnIdlePipTransitionStateRunnable;
+
/**
* An interface to track state updates as we progress through PiP transitions.
*/
@@ -197,6 +203,8 @@
mState = state;
dispatchPipTransitionStateChanged(prevState, mState, extra);
}
+
+ maybeRunOnIdlePipTransitionStateCallback();
}
/**
@@ -231,6 +239,29 @@
}
/**
+ * Schedule a callback to run when in a valid idle PiP state.
+ *
+ * <p>We only allow for one callback to be scheduled to avoid cases with multiple transitions
+ * being scheduled. For instance, if user double taps and IME shows, this would
+ * schedule a bounds change transition for IME appearing. But if some other transition would
+ * want to animate PiP before the scheduled callback executes, we would rather want to replace
+ * the existing callback with a new one, to avoid multiple animations
+ * as soon as we are idle.</p>
+ */
+ public void setOnIdlePipTransitionStateRunnable(
+ @Nullable Runnable onIdlePipTransitionStateRunnable) {
+ mOnIdlePipTransitionStateRunnable = onIdlePipTransitionStateRunnable;
+ maybeRunOnIdlePipTransitionStateCallback();
+ }
+
+ private void maybeRunOnIdlePipTransitionStateCallback() {
+ if (mOnIdlePipTransitionStateRunnable != null && isPipStateIdle()) {
+ mOnIdlePipTransitionStateRunnable.run();
+ mOnIdlePipTransitionStateRunnable = null;
+ }
+ }
+
+ /**
* Adds a {@link PipTransitionStateChangedListener} for future PiP transition state updates.
*/
public void addPipTransitionStateChangedListener(PipTransitionStateChangedListener listener) {
@@ -318,6 +349,11 @@
throw new IllegalStateException("Unknown state: " + state);
}
+ public boolean isPipStateIdle() {
+ // This needs to be a valid in-PiP state that isn't a transient state.
+ return mState == ENTERED_PIP || mState == CHANGED_PIP_BOUNDS;
+ }
+
@Override
public String toString() {
return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 0a5672d..7672a8f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -468,8 +468,65 @@
}
@Test
+ fun addOrMoveFreeformTaskToTop_taskIsMinimized_unminimizesTask() {
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 7)
+ repo.minimizeTask(displayId = 0, taskId = 6)
+
+ val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
+ assertThat(tasks).containsExactly(7, 6, 5).inOrder()
+ assertThat(repo.isMinimizedTask(taskId = 6)).isTrue()
+ }
+
+ @Test
+ fun addOrMoveFreeformTaskToTop_taskIsUnminimized_noop() {
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 7)
+
+ val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
+ assertThat(tasks).containsExactly(7, 6, 5).inOrder()
+ assertThat(repo.isMinimizedTask(taskId = 6)).isFalse()
+ }
+
+ @Test
+ fun removeFreeformTask_invalidDisplay_removesTaskFromFreeformTasks() {
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+
+ repo.removeFreeformTask(INVALID_DISPLAY, taskId = 1)
+
+ val invalidDisplayTasks = repo.getFreeformTasksInZOrder(INVALID_DISPLAY)
+ assertThat(invalidDisplayTasks).isEmpty()
+ val validDisplayTasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
+ assertThat(validDisplayTasks).isEmpty()
+ }
+
+ @Test
+ fun removeFreeformTask_validDisplay_removesTaskFromFreeformTasks() {
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+
+ repo.removeFreeformTask(DEFAULT_DISPLAY, taskId = 1)
+
+ val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
+ assertThat(tasks).isEmpty()
+ }
+
+ @Test
+ fun removeFreeformTask_validDisplay_differentDisplay_doesNotRemovesTask() {
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+
+ repo.removeFreeformTask(SECOND_DISPLAY, taskId = 1)
+
+ val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
+ assertThat(tasks).containsExactly(1)
+ }
+
+ @Test
fun removeFreeformTask_removesTaskBoundsBeforeMaximize() {
val taskId = 1
+ repo.addActiveTask(THIRD_DISPLAY, taskId)
+ repo.addOrMoveFreeformTaskToTop(THIRD_DISPLAY, taskId)
repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200))
repo.removeFreeformTask(THIRD_DISPLAY, taskId)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index bd39aa6..2dea43b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -61,20 +61,23 @@
@Test
fun testFullscreenRegionCalculation() {
- val transitionHeight = context.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_fullscreen_from_desktop_height)
- val fromFreeformWidth = mContext.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_fullscreen_from_desktop_width
- )
var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT)
+
+ val transitionHeight = context.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_transition_region_thickness)
+ val toFullscreenScale = mContext.resources.getFloat(
+ R.dimen.desktop_mode_fullscreen_region_scale
+ )
+ val toFullscreenWidth = displayLayout.width() * toFullscreenScale
+
assertThat(testRegion.bounds).isEqualTo(Rect(
- DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
+ (DISPLAY_BOUNDS.width() / 2f - toFullscreenWidth / 2f).toInt(),
-50,
- DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
+ (DISPLAY_BOUNDS.width() / 2f + toFullscreenWidth / 2f).toInt(),
transitionHeight))
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index e302fa8..d71f3b6 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -736,6 +736,7 @@
cc_test {
name: "hwui_unit_tests",
+ test_config: "tests/unit/AndroidTest.xml",
defaults: [
"hwui_test_defaults",
"android_graphics_apex",
@@ -803,6 +804,7 @@
cc_benchmark {
name: "hwuimacro",
+ test_config: "tests/macrobench/AndroidTest.xml",
defaults: ["hwui_test_defaults"],
static_libs: ["libhwui"],
@@ -822,6 +824,7 @@
cc_benchmark {
name: "hwuimicro",
+ test_config: "tests/microbench/AndroidTest.xml",
defaults: ["hwui_test_defaults"],
static_libs: ["libhwui_static"],
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 5d3bc89..d184f64 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -101,6 +101,8 @@
bool Properties::clipSurfaceViews = false;
bool Properties::hdr10bitPlus = false;
+int Properties::timeoutMultiplier = 1;
+
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;
@@ -174,6 +176,8 @@
base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews());
hdr10bitPlus = hwui_flags::hdr_10bit_plus();
+ timeoutMultiplier = android::base::GetIntProperty("ro.hw_timeout_multiplier", 1);
+
return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index d3176f6..e264642 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -343,6 +343,8 @@
static bool clipSurfaceViews;
static bool hdr10bitPlus;
+ static int timeoutMultiplier;
+
static StretchEffectBehavior getStretchEffectBehavior() {
return stretchEffectBehavior;
}
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index afe4c38..2f15722 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -91,8 +91,10 @@
{
ATRACE_NAME("sync_wait");
- if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) {
- ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
+ int syncWaitTimeoutMs = 500 * Properties::timeoutMultiplier;
+ if (sourceFence != -1 && sync_wait(sourceFence.get(), syncWaitTimeoutMs) != NO_ERROR) {
+ ALOGE("Timeout (%dms) exceeded waiting for buffer fence, abandoning readback attempt",
+ syncWaitTimeoutMs);
return request->onCopyFinished(CopyResult::Timeout);
}
}
@@ -109,9 +111,8 @@
sk_sp<SkColorSpace> colorSpace =
DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace));
- sk_sp<SkImage> image =
- SkImages::DeferredFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType,
- colorSpace);
+ sk_sp<SkImage> image = SkImages::DeferredFromAHardwareBuffer(sourceBuffer.get(),
+ kPremul_SkAlphaType, colorSpace);
if (!image.get()) {
return request->onCopyFinished(CopyResult::UnknownError);
diff --git a/libs/hwui/AndroidTest.xml b/libs/hwui/tests/macrobench/AndroidTest.xml
similarity index 60%
copy from libs/hwui/AndroidTest.xml
copy to libs/hwui/tests/macrobench/AndroidTest.xml
index 75f61f5..5b8576d 100644
--- a/libs/hwui/AndroidTest.xml
+++ b/libs/hwui/tests/macrobench/AndroidTest.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!-- Copyright 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.
@@ -13,24 +13,13 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<configuration description="Config for hwuimicro">
+<configuration description="Config for hwuimacro">
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
- <option name="push" value="hwui_unit_tests->/data/local/tmp/nativetest/hwui_unit_tests" />
- <option name="push" value="hwuimicro->/data/local/tmp/benchmarktest/hwuimicro" />
<option name="push" value="hwuimacro->/data/local/tmp/benchmarktest/hwuimacro" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
<option name="not-shardable" value="true" />
- <test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/local/tmp/nativetest" />
- <option name="module-name" value="hwui_unit_tests" />
- </test>
- <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
- <option name="native-benchmark-device-path" value="/data/local/tmp/benchmarktest" />
- <option name="benchmark-module-name" value="hwuimicro" />
- <option name="file-exclusion-filter-regex" value=".*\.config$" />
- </test>
<test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
<option name="native-benchmark-device-path" value="/data/local/tmp/benchmarktest" />
<option name="benchmark-module-name" value="hwuimacro" />
diff --git a/libs/hwui/tests/macrobench/how_to_run.txt b/libs/hwui/tests/macrobench/how_to_run.txt
index 3c3d36a..59ef25a 100644
--- a/libs/hwui/tests/macrobench/how_to_run.txt
+++ b/libs/hwui/tests/macrobench/how_to_run.txt
@@ -3,3 +3,7 @@
adb shell /data/benchmarktest/hwuimacro/hwuimacro shadowgrid2 --onscreen
Pass --help to get help
+
+OR (if you don't need to pass arguments)
+
+atest hwuimacro
diff --git a/libs/hwui/AndroidTest.xml b/libs/hwui/tests/microbench/AndroidTest.xml
similarity index 63%
rename from libs/hwui/AndroidTest.xml
rename to libs/hwui/tests/microbench/AndroidTest.xml
index 75f61f5..d67305df 100644
--- a/libs/hwui/AndroidTest.xml
+++ b/libs/hwui/tests/microbench/AndroidTest.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!-- Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,24 +16,13 @@
<configuration description="Config for hwuimicro">
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
- <option name="push" value="hwui_unit_tests->/data/local/tmp/nativetest/hwui_unit_tests" />
<option name="push" value="hwuimicro->/data/local/tmp/benchmarktest/hwuimicro" />
- <option name="push" value="hwuimacro->/data/local/tmp/benchmarktest/hwuimacro" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
<option name="not-shardable" value="true" />
- <test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/local/tmp/nativetest" />
- <option name="module-name" value="hwui_unit_tests" />
- </test>
<test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
<option name="native-benchmark-device-path" value="/data/local/tmp/benchmarktest" />
<option name="benchmark-module-name" value="hwuimicro" />
<option name="file-exclusion-filter-regex" value=".*\.config$" />
</test>
- <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
- <option name="native-benchmark-device-path" value="/data/local/tmp/benchmarktest" />
- <option name="benchmark-module-name" value="hwuimacro" />
- <option name="file-exclusion-filter-regex" value=".*\.config$" />
- </test>
</configuration>
diff --git a/libs/hwui/tests/microbench/how_to_run.txt b/libs/hwui/tests/microbench/how_to_run.txt
index 915fe5d..c7ddc1a 100755
--- a/libs/hwui/tests/microbench/how_to_run.txt
+++ b/libs/hwui/tests/microbench/how_to_run.txt
@@ -1,3 +1,7 @@
mmm -j8 frameworks/base/libs/hwui &&
adb push $OUT/data/benchmarktest/hwuimicro/hwuimicro /data/benchmarktest/hwuimicro/hwuimicro &&
adb shell /data/benchmarktest/hwuimicro/hwuimicro
+
+OR
+
+atest hwuimicro
diff --git a/libs/hwui/tests/unit/AndroidTest.xml b/libs/hwui/tests/unit/AndroidTest.xml
new file mode 100644
index 0000000..dc586c9
--- /dev/null
+++ b/libs/hwui/tests/unit/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for hwui_unit_tests">
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="hwui_unit_tests->/data/local/tmp/nativetest/hwui_unit_tests" />
+ </target_preparer>
+ <option name="test-suite-tag" value="apct" />
+ <option name="not-shardable" value="true" />
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp/nativetest" />
+ <option name="module-name" value="hwui_unit_tests" />
+ </test>
+</configuration>
diff --git a/libs/hwui/tests/unit/how_to_run.txt b/libs/hwui/tests/unit/how_to_run.txt
index c11d6eb3..1a35adf 100755
--- a/libs/hwui/tests/unit/how_to_run.txt
+++ b/libs/hwui/tests/unit/how_to_run.txt
@@ -2,3 +2,11 @@
adb push $ANDROID_PRODUCT_OUT/data/nativetest/hwui_unit_tests/hwui_unit_tests \
/data/nativetest/hwui_unit_tests/hwui_unit_tests &&
adb shell /data/nativetest/hwui_unit_tests/hwui_unit_tests
+
+OR
+
+atest hwui_unit_tests
+
+OR, if you need arguments, they can be passed as native-test-flags, as in:
+
+atest hwui_unit_tests -- --test-arg com.android.tradefed.testtype.GTest:native-test-flag:"--renderer=skiavk"
diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp
index 76cbc8a..3fd15c4 100644
--- a/libs/hwui/tests/unit/main.cpp
+++ b/libs/hwui/tests/unit/main.cpp
@@ -15,6 +15,7 @@
*/
#include <getopt.h>
+#include <log/log.h>
#include <signal.h>
#include "Properties.h"
@@ -65,6 +66,19 @@
return RenderPipelineType::SkiaGL;
}
+static constexpr const char* renderPipelineTypeName(const RenderPipelineType renderPipelineType) {
+ switch (renderPipelineType) {
+ case RenderPipelineType::SkiaGL:
+ return "SkiaGL";
+ case RenderPipelineType::SkiaVulkan:
+ return "SkiaVulkan";
+ case RenderPipelineType::SkiaCpu:
+ return "SkiaCpu";
+ case RenderPipelineType::NotInitialized:
+ return "NotInitialized";
+ }
+}
+
struct Options {
RenderPipelineType renderer = RenderPipelineType::SkiaGL;
};
@@ -118,6 +132,7 @@
auto opts = parseOptions(argc, argv);
Properties::overrideRenderPipelineType(opts.renderer);
+ ALOGI("Starting HWUI unit tests with %s pipeline", renderPipelineTypeName(opts.renderer));
// Run the tests
testing::InitGoogleTest(&argc, argv);
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 4217562..1024a55 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -578,6 +578,8 @@
});
private AudioAttributes() {
+ mBundle = null;
+ mFormattedTags = "";
}
/**
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 395f81d..0ffab4b 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1166,10 +1166,11 @@
/**
- * Returns whether the device supports observer mode or not. When observe
- * mode is enabled, the NFC hardware will listen for NFC readers, but not
- * respond to them. When observe mode is disabled, the NFC hardware will
- * resoond to the reader and proceed with the transaction.
+ * Returns whether the device supports observe mode or not. When observe mode is enabled, the
+ * NFC hardware will listen to NFC readers, but not respond to them. While enabled, observed
+ * polling frames will be sent to the APDU service (see {@link #setObserveModeEnabled(boolean)}.
+ * When observe mode is disabled (or if it's not supported), the NFC hardware will automatically
+ * respond to the reader and proceed with the transaction.
* @return true if the mode is supported, false otherwise.
*/
@FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
@@ -1193,9 +1194,10 @@
* and simply observe and notify the APDU service of polling loop frames. See
* {@link #isObserveModeSupported()} for a description of observe mode. Only the package of the
* currently preferred service (the service set as preferred by the current foreground
- * application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the
- * current Default Wallet Role Holder {@link android.app.role.RoleManager#ROLE_WALLET}),
- * otherwise a call to this method will fail and return false.
+ * application via {@link android.nfc.cardemulation.CardEmulation#setPreferredService(Activity,
+ * android.content.ComponentName)} or the current Default Wallet Role Holder
+ * {@link android.app.role.RoleManager#ROLE_WALLET}), otherwise a call to this method will fail
+ * and return false.
*
* @param enabled false disables observe mode to allow the transaction to proceed while true
* enables observe mode and does not allow transactions to proceed.
diff --git a/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm b/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
index 9c2064c..8c6880b 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
@@ -58,7 +58,8 @@
label: '5'
base: '\u0665'
capslock: '5'
- shift: '%'
+ shift: '\u066a'
+ shift+capslock: '%'
}
key 6 {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index ce997bf..5c4cdb2 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1010,8 +1010,8 @@
<!-- UI debug setting: force allow on external summary [CHAR LIMIT=150] -->
<string name="force_resizable_activities_summary">Make all activities resizable for multi-window, regardless of manifest values.</string>
- <!-- Title for a toggle that enables support for windows to be in freeform (apps run in resizable windows). [CHAR LIMIT=50] -->
- <string name="enable_freeform_support">Enable freeform window support</string>
+ <!-- Title for a toggle that enables support for windows to be in freeform. Freeform windows enables users to freely arrange and resize overlapping apps. [CHAR LIMIT=50] -->
+ <string name="enable_freeform_support">Enable freeform windows</string>
<!-- Local (desktop) backup password menu title [CHAR LIMIT=25] -->
<string name="local_backup_password_title">Desktop backup password</string>
@@ -1164,7 +1164,7 @@
<!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
<string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> left until full</string>
<!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited -->
- <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging optimized</string>
+ <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging on hold to protect battery</string>
<!-- [CHAR_LIMIT=80] Label for battery charging future pause -->
<string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 732b358..88af7ee 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -158,6 +158,11 @@
mIsManualDnd = isManualDnd;
}
+ /** Creates a deep copy of this object. */
+ public ZenMode copy() {
+ return new ZenMode(mId, new AutomaticZenRule.Builder(mRule).build(), mStatus, mIsManualDnd);
+ }
+
@NonNull
public String getId() {
return mId;
diff --git a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
index d69c87b..2dc2650 100644
--- a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
@@ -21,6 +21,7 @@
import android.content.Intent
import android.os.OutcomeReceiver
import android.telephony.satellite.SatelliteManager
+import android.telephony.satellite.SatelliteModemStateCallback
import android.util.Log
import android.view.WindowManager
import androidx.lifecycle.LifecycleOwner
@@ -31,12 +32,19 @@
import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.Job
import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeoutException
import kotlin.coroutines.resume
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOn
/** A util for Satellite dialog */
object SatelliteDialogUtils {
@@ -70,7 +78,7 @@
coroutineScope.launch {
var isSatelliteModeOn = false
try {
- isSatelliteModeOn = requestIsEnabled(context)
+ isSatelliteModeOn = requestIsSessionStarted(context)
} catch (e: InterruptedException) {
Log.w(TAG, "Error to get satellite status : $e")
} catch (e: ExecutionException) {
@@ -134,6 +142,70 @@
}
}
+ private suspend fun requestIsSessionStarted(
+ context: Context
+ ): Boolean = withContext(Default) {
+ getIsSessionStartedFlow(context).conflate().first()
+ }
+
+ /**
+ * Provides a Flow that emits the session state of the satellite modem. Updates are triggered
+ * when the modem state changes.
+ *
+ * @param defaultDispatcher The CoroutineDispatcher to use (Defaults to `Dispatchers.Default`).
+ * @return A Flow emitting `true` when the session is started and `false` otherwise.
+ */
+ private fun getIsSessionStartedFlow(
+ context: Context
+ ): Flow<Boolean> {
+ val satelliteManager: SatelliteManager? =
+ context.getSystemService(SatelliteManager::class.java)
+ if (satelliteManager == null) {
+ Log.w(TAG, "SatelliteManager is null")
+ return flowOf(false)
+ }
+
+ return callbackFlow {
+ val callback = SatelliteModemStateCallback { state ->
+ val isSessionStarted = isSatelliteSessionStarted(state)
+ Log.i(TAG, "Satellite modem state changed: state=$state"
+ + ", isSessionStarted=$isSessionStarted")
+ trySend(isSessionStarted)
+ }
+
+ val registerResult = satelliteManager.registerForModemStateChanged(
+ Default.asExecutor(),
+ callback
+ )
+
+ if (registerResult != SatelliteManager.SATELLITE_RESULT_SUCCESS) {
+ // If the registration failed (e.g., device doesn't support satellite),
+ // SatelliteManager will not emit the current state by callback.
+ // We send `false` value by ourself to make sure the flow has initial value.
+ Log.w(TAG, "Failed to register for satellite modem state change: $registerResult")
+ trySend(false)
+ }
+
+ awaitClose { satelliteManager.unregisterForModemStateChanged(callback) }
+ }.flowOn(Default)
+ }
+
+
+ /**
+ * Check if the modem is in a satellite session.
+ *
+ * @param state The SatelliteModemState provided by the SatelliteManager.
+ * @return `true` if the modem is in a satellite session, `false` otherwise.
+ */
+ fun isSatelliteSessionStarted(@SatelliteManager.SatelliteModemState state: Int): Boolean {
+ return when (state) {
+ SatelliteManager.SATELLITE_MODEM_STATE_OFF,
+ SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE,
+ SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN -> false
+ else -> true
+ }
+ }
+
const val TAG = "SatelliteDialogUtils"
const val EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG: String =
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
index aeda1ed6..31d7130 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
@@ -17,11 +17,12 @@
package com.android.settingslib.satellite
import android.content.Context
-import android.content.Intent
-import android.os.OutcomeReceiver
import android.platform.test.annotations.RequiresFlagsEnabled
import android.telephony.satellite.SatelliteManager
-import android.telephony.satellite.SatelliteManager.SatelliteException
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_ENABLING_SATELLITE
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF
+import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_ERROR
+import android.telephony.satellite.SatelliteModemStateCallback
import android.util.AndroidRuntimeException
import androidx.test.core.app.ApplicationProvider
import com.android.internal.telephony.flags.Flags
@@ -67,26 +68,21 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteIsOn_showWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
.thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+ val callback = invocation
+ .getArgument<SatelliteModemStateCallback>(
1
)
- receiver.onResult(true)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_ENABLING_SATELLITE)
null
}
try {
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertTrue(it)
- })
+ assertTrue(it)
+ })
} catch (e: AndroidRuntimeException) {
// Catch exception of starting activity .
}
@@ -95,68 +91,49 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteIsOff_notShowWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
.thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+ val callback = invocation
+ .getArgument<SatelliteModemStateCallback>(
1
)
- receiver.onResult(false)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_OFF)
null
}
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_noSatelliteManager_notShowWarningDialog() = runBlocking {
- `when`(context.getSystemService(SatelliteManager::class.java))
- .thenReturn(null)
+ `when`(context.getSystemService(SatelliteManager::class.java)).thenReturn(null)
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteErrorResult_notShowWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
- .thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
- 1
- )
- receiver.onError(SatelliteException(SatelliteManager.SATELLITE_RESULT_ERROR))
- null
- }
-
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
+ .thenReturn(SATELLITE_RESULT_MODEM_ERROR)
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 3be5231..368085f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -32,6 +32,7 @@
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
@@ -70,7 +71,7 @@
}
object AllElements : ElementMatcher {
- override fun matches(key: ElementKey, scene: SceneKey) = true
+ override fun matches(key: ElementKey, content: ContentKey) = true
}
private object TransitionDuration {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 859c036..df068c4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -92,7 +92,7 @@
fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) {
val areNotificationsVisible by
lockscreenContentViewModel
- .areNotificationsVisible(sceneKey)
+ .areNotificationsVisible(contentKey)
.collectAsStateWithLifecycle(initialValue = false)
if (!areNotificationsVisible) {
return
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
new file mode 100644
index 0000000..4b3a39b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.notifications.ui.composable
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.offset
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.unit.IntOffset
+import com.android.compose.nestedscroll.PriorityNestedScrollConnection
+import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+@Composable
+fun Modifier.stackVerticalOverscroll(
+ coroutineScope: CoroutineScope,
+ canScrollForward: () -> Boolean
+): Modifier {
+ val overscrollOffset = remember { Animatable(0f) }
+ val stackNestedScrollConnection = remember {
+ NotificationStackNestedScrollConnection(
+ stackOffset = { overscrollOffset.value },
+ canScrollForward = canScrollForward,
+ onScroll = { offsetAvailable ->
+ coroutineScope.launch {
+ overscrollOffset.snapTo(overscrollOffset.value + offsetAvailable * 0.3f)
+ }
+ },
+ onStop = { velocityAvailable ->
+ coroutineScope.launch {
+ overscrollOffset.animateTo(
+ targetValue = 0f,
+ initialVelocity = velocityAvailable,
+ animationSpec = tween()
+ )
+ }
+ }
+ )
+ }
+
+ return this.then(
+ Modifier.nestedScroll(stackNestedScrollConnection).offset {
+ IntOffset(x = 0, y = overscrollOffset.value.roundToInt())
+ }
+ )
+}
+
+fun NotificationStackNestedScrollConnection(
+ stackOffset: () -> Float,
+ canScrollForward: () -> Boolean,
+ onStart: (Float) -> Unit = {},
+ onScroll: (Float) -> Unit,
+ onStop: (Float) -> Unit = {},
+): PriorityNestedScrollConnection {
+ return PriorityNestedScrollConnection(
+ orientation = Orientation.Vertical,
+ canStartPreScroll = { _, _ -> false },
+ canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
+ offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward()
+ },
+ canStartPostFling = { velocityAvailable -> velocityAvailable < 0f && !canScrollForward() },
+ canContinueScroll = { source ->
+ if (source == NestedScrollSource.SideEffect) {
+ stackOffset() > STACK_OVERSCROLL_FLING_MIN_OFFSET
+ } else {
+ true
+ }
+ },
+ canScrollOnFling = true,
+ onStart = { offsetAvailable -> onStart(offsetAvailable) },
+ onScroll = { offsetAvailable ->
+ onScroll(offsetAvailable)
+ offsetAvailable
+ },
+ onStop = { velocityAvailable ->
+ onStop(velocityAvailable)
+ velocityAvailable
+ },
+ )
+}
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 76a7a10..2eb7b3f 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
@@ -474,6 +474,7 @@
.thenIf(shadeMode == ShadeMode.Single) {
Modifier.nestedScroll(scrimNestedScrollConnection)
}
+ .stackVerticalOverscroll(coroutineScope) { scrollState.canScrollForward }
.verticalScroll(scrollState)
.padding(top = topPadding)
.fillMaxWidth()
@@ -671,3 +672,4 @@
private val DEBUG_BOX_COLOR = Color(0f, 1f, 0f, 0.2f)
private const val HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION = 0.25f
private const val HUN_SNOOZE_VELOCITY_THRESHOLD = -70f
+internal const val STACK_OVERSCROLL_FLING_MIN_OFFSET = -100f
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 114dcf4..afbc8e7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -67,15 +67,15 @@
/**
* Animate a scene Int value.
*
- * @see SceneScope.animateSceneValueAsState
+ * @see ContentScope.animateContentValueAsState
*/
@Composable
-fun SceneScope.animateSceneIntAsState(
+fun ContentScope.animateContentIntAsState(
value: Int,
key: ValueKey,
canOverflow: Boolean = true,
): AnimatedState<Int> {
- return animateSceneValueAsState(value, key, SharedIntType, canOverflow)
+ return animateContentValueAsState(value, key, SharedIntType, canOverflow)
}
/**
@@ -107,17 +107,28 @@
/**
* Animate a scene Float value.
*
- * @see SceneScope.animateSceneValueAsState
+ * @see ContentScope.animateContentValueAsState
*/
@Composable
-fun SceneScope.animateSceneFloatAsState(
+fun ContentScope.animateContentFloatAsState(
value: Float,
key: ValueKey,
canOverflow: Boolean = true,
): AnimatedState<Float> {
- return animateSceneValueAsState(value, key, SharedFloatType, canOverflow)
+ return animateContentValueAsState(value, key, SharedFloatType, canOverflow)
}
+@Deprecated(
+ "Use animateSceneFloatAsState() instead",
+ replaceWith = ReplaceWith("animateContentFloatAsState(value, key, canOverflow)")
+)
+@Composable
+fun ContentScope.animateSceneFloatAsState(
+ value: Float,
+ key: ValueKey,
+ canOverflow: Boolean = true,
+) = animateContentFloatAsState(value, key, canOverflow)
+
/**
* Animate a shared element Float value.
*
@@ -147,17 +158,28 @@
/**
* Animate a scene Dp value.
*
- * @see SceneScope.animateSceneValueAsState
+ * @see ContentScope.animateContentValueAsState
*/
@Composable
-fun SceneScope.animateSceneDpAsState(
+fun ContentScope.animateContentDpAsState(
value: Dp,
key: ValueKey,
canOverflow: Boolean = true,
): AnimatedState<Dp> {
- return animateSceneValueAsState(value, key, SharedDpType, canOverflow)
+ return animateContentValueAsState(value, key, SharedDpType, canOverflow)
}
+@Deprecated(
+ "Use animateSceneDpAsState() instead",
+ replaceWith = ReplaceWith("animateContentDpAsState(value, key, canOverflow)")
+)
+@Composable
+fun ContentScope.animateSceneDpAsState(
+ value: Dp,
+ key: ValueKey,
+ canOverflow: Boolean = true,
+) = animateContentDpAsState(value, key, canOverflow)
+
/**
* Animate a shared element Dp value.
*
@@ -188,14 +210,14 @@
/**
* Animate a scene Color value.
*
- * @see SceneScope.animateSceneValueAsState
+ * @see ContentScope.animateContentValueAsState
*/
@Composable
-fun SceneScope.animateSceneColorAsState(
+fun ContentScope.animateContentColorAsState(
value: Color,
key: ValueKey,
): AnimatedState<Color> {
- return animateSceneValueAsState(value, key, SharedColorType, canOverflow = false)
+ return animateContentValueAsState(value, key, SharedColorType, canOverflow = false)
}
/**
@@ -261,24 +283,24 @@
@Composable
internal fun <T> animateSharedValueAsState(
layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
+ content: ContentKey,
element: ElementKey?,
key: ValueKey,
value: T,
type: SharedValueType<T, *>,
canOverflow: Boolean,
): AnimatedState<T> {
- DisposableEffect(layoutImpl, scene, element, key) {
- // Create the associated maps that hold the current value for each (element, scene) pair.
+ DisposableEffect(layoutImpl, content, element, key) {
+ // Create the associated maps that hold the current value for each (element, content) pair.
val valueMap = layoutImpl.sharedValues.getOrPut(key) { mutableMapOf() }
val sharedValue = valueMap.getOrPut(element) { SharedValue(type) } as SharedValue<T, *>
val targetValues = sharedValue.targetValues
- targetValues[scene] = value
+ targetValues[content] = value
onDispose {
// Remove the value associated to the current scene, and eventually remove the maps if
// they are empty.
- targetValues.remove(scene)
+ targetValues.remove(content)
if (targetValues.isEmpty() && valueMap[element] === sharedValue) {
valueMap.remove(element)
@@ -297,11 +319,11 @@
error("value is equal to $value, which is the undefined value for this type.")
}
- sharedValue<T, Any>(layoutImpl, key, element).targetValues[scene] = value
+ sharedValue<T, Any>(layoutImpl, key, element).targetValues[content] = value
}
- return remember(layoutImpl, scene, element, canOverflow) {
- AnimatedStateImpl<T, Any>(layoutImpl, scene, element, key, canOverflow)
+ return remember(layoutImpl, content, element, canOverflow) {
+ AnimatedStateImpl<T, Any>(layoutImpl, content, element, key, canOverflow)
}
}
@@ -322,8 +344,8 @@
internal class SharedValue<T, Delta>(
val type: SharedValueType<T, Delta>,
) {
- /** The target value of this shared value for each scene. */
- val targetValues = SnapshotStateMap<SceneKey, T>()
+ /** The target value of this shared value for each content. */
+ val targetValues = SnapshotStateMap<ContentKey, T>()
/** The last value of this shared value. */
var lastValue: T = type.unspecifiedValue
@@ -340,7 +362,7 @@
private class AnimatedStateImpl<T, Delta>(
private val layoutImpl: SceneTransitionLayoutImpl,
- private val scene: SceneKey,
+ private val content: ContentKey,
private val element: ElementKey?,
private val key: ValueKey,
private val canOverflow: Boolean,
@@ -356,14 +378,14 @@
// TODO(b/311600838): Remove this. We should not have to fallback to the current
// scene value, but we have to because code of removed nodes can still run if they
// are placed with a graphics layer.
- ?: sharedValue[scene]
+ ?: sharedValue[content]
?: error(valueReadTooEarlyMessage(key))
val interruptedValue = computeInterruptedValue(sharedValue, transition, value)
sharedValue.lastValue = interruptedValue
return interruptedValue
}
- private operator fun SharedValue<T, *>.get(scene: SceneKey): T? = targetValues[scene]
+ private operator fun SharedValue<T, *>.get(content: ContentKey): T? = targetValues[content]
private fun valueOrNull(
sharedValue: SharedValue<T, *>,
@@ -401,7 +423,7 @@
val targetValues = sharedValue.targetValues
val transition =
if (element != null) {
- layoutImpl.elements[element]?.sceneStates?.let { sceneStates ->
+ layoutImpl.elements[element]?.stateByContent?.let { sceneStates ->
layoutImpl.state.currentTransitions.fastLastOrNull { transition ->
transition.fromScene in sceneStates || transition.toScene in sceneStates
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index fb13b57..67d1b59 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -30,6 +30,7 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
+import com.android.compose.animation.scene.content.Scene
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 3ad07d0..0b5e58f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -48,6 +48,7 @@
import androidx.compose.ui.util.fastCoerceIn
import androidx.compose.ui.util.fastLastOrNull
import androidx.compose.ui.util.lerp
+import com.android.compose.animation.scene.content.Content
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.ui.util.lerp
@@ -57,30 +58,30 @@
/** An element on screen, that can be composed in one or more scenes. */
@Stable
internal class Element(val key: ElementKey) {
- /** The mapping between a scene and the state this element has in that scene, if any. */
+ /** The mapping between a content and the state this element has in that content, if any. */
// TODO(b/316901148): Make this a normal map instead once we can make sure that new transitions
// are first seen by composition then layout/drawing code. See b/316901148#comment2 for details.
- val sceneStates = SnapshotStateMap<SceneKey, SceneState>()
+ val stateByContent = SnapshotStateMap<ContentKey, State>()
/**
* The last transition that was used when computing the state (size, position and alpha) of this
- * element in any scene, or `null` if it was last laid out when idle.
+ * element in any content, or `null` if it was last laid out when idle.
*/
var lastTransition: TransitionState.Transition? = null
- /** Whether this element was ever drawn in a scene. */
- var wasDrawnInAnyScene = false
+ /** Whether this element was ever drawn in a content. */
+ var wasDrawnInAnyContent = false
override fun toString(): String {
return "Element(key=$key)"
}
- /** The last and target state of this element in a given scene. */
+ /** The last and target state of this element in a given content. */
@Stable
- class SceneState(val scene: SceneKey) {
+ class State(val content: ContentKey) {
/**
- * The *target* state of this element in this scene, i.e. the state of this element when we
- * are idle on this scene.
+ * The *target* state of this element in this content, i.e. the state of this element when
+ * we are idle on this content.
*/
var targetSize by mutableStateOf(SizeUnspecified)
var targetOffset by mutableStateOf(Offset.Unspecified)
@@ -91,7 +92,9 @@
var lastScale = Scale.Unspecified
var lastAlpha = AlphaUnspecified
- /** The state of this element in this scene right before the last interruption (if any). */
+ /**
+ * The state of this element in this content right before the last interruption (if any).
+ */
var offsetBeforeInterruption = Offset.Unspecified
var sizeBeforeInterruption = SizeUnspecified
var scaleBeforeInterruption = Scale.Unspecified
@@ -109,7 +112,7 @@
var alphaInterruptionDelta = 0f
/**
- * The attached [ElementNode] a Modifier.element() for a given element and scene. During
+ * The attached [ElementNode] a Modifier.element() for a given element and content. During
* composition, this set could have 0 to 2 elements. After composition and after all
* modifier nodes have been attached/detached, this set should contain exactly 1 element.
*/
@@ -130,19 +133,19 @@
}
}
-/** The implementation of [SceneScope.element]. */
+/** The implementation of [ContentScope.element]. */
@Stable
internal fun Modifier.element(
layoutImpl: SceneTransitionLayoutImpl,
- scene: Scene,
+ content: Content,
key: ElementKey,
): Modifier {
// Make sure that we read the current transitions during composition and not during
// layout/drawing.
// TODO(b/341072461): Revert this and read the current transitions in ElementNode directly once
- // we can ensure that SceneTransitionLayoutImpl will compose new scenes first.
+ // we can ensure that SceneTransitionLayoutImpl will compose new contents first.
val currentTransitions = layoutImpl.state.currentTransitions
- return then(ElementModifier(layoutImpl, currentTransitions, scene, key)).testTag(key.testTag)
+ return then(ElementModifier(layoutImpl, currentTransitions, content, key)).testTag(key.testTag)
}
/**
@@ -152,92 +155,92 @@
private data class ElementModifier(
private val layoutImpl: SceneTransitionLayoutImpl,
private val currentTransitions: List<TransitionState.Transition>,
- private val scene: Scene,
+ private val content: Content,
private val key: ElementKey,
) : ModifierNodeElement<ElementNode>() {
- override fun create(): ElementNode = ElementNode(layoutImpl, currentTransitions, scene, key)
+ override fun create(): ElementNode = ElementNode(layoutImpl, currentTransitions, content, key)
override fun update(node: ElementNode) {
- node.update(layoutImpl, currentTransitions, scene, key)
+ node.update(layoutImpl, currentTransitions, content, key)
}
}
internal class ElementNode(
private var layoutImpl: SceneTransitionLayoutImpl,
private var currentTransitions: List<TransitionState.Transition>,
- private var scene: Scene,
+ private var content: Content,
private var key: ElementKey,
) : Modifier.Node(), DrawModifierNode, ApproachLayoutModifierNode, TraversableNode {
private var _element: Element? = null
private val element: Element
get() = _element!!
- private var _sceneState: Element.SceneState? = null
- private val sceneState: Element.SceneState
- get() = _sceneState!!
+ private var _stateInContent: Element.State? = null
+ private val stateInContent: Element.State
+ get() = _stateInContent!!
override val traverseKey: Any = ElementTraverseKey
override fun onAttach() {
super.onAttach()
- updateElementAndSceneValues()
- addNodeToSceneState()
+ updateElementAndContentValues()
+ addNodeToContentState()
}
- private fun updateElementAndSceneValues() {
+ private fun updateElementAndContentValues() {
val element =
layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it }
_element = element
- _sceneState =
- element.sceneStates[scene.key]
- ?: Element.SceneState(scene.key).also { element.sceneStates[scene.key] = it }
+ _stateInContent =
+ element.stateByContent[content.key]
+ ?: Element.State(content.key).also { element.stateByContent[content.key] = it }
}
- private fun addNodeToSceneState() {
- sceneState.nodes.add(this)
+ private fun addNodeToContentState() {
+ stateInContent.nodes.add(this)
coroutineScope.launch {
// At this point all [CodeLocationNode] have been attached or detached, which means that
- // [sceneState.codeLocations] should have exactly 1 element, otherwise this means that
- // this element was composed multiple times in the same scene.
- val nCodeLocations = sceneState.nodes.size
- if (nCodeLocations != 1 || !sceneState.nodes.contains(this@ElementNode)) {
- error("$key was composed $nCodeLocations times in ${sceneState.scene}")
+ // [elementState.codeLocations] should have exactly 1 element, otherwise this means that
+ // this element was composed multiple times in the same content.
+ val nCodeLocations = stateInContent.nodes.size
+ if (nCodeLocations != 1 || !stateInContent.nodes.contains(this@ElementNode)) {
+ error("$key was composed $nCodeLocations times in ${stateInContent.content}")
}
}
}
override fun onDetach() {
super.onDetach()
- removeNodeFromSceneState()
- maybePruneMaps(layoutImpl, element, sceneState)
+ removeNodeFromContentState()
+ maybePruneMaps(layoutImpl, element, stateInContent)
_element = null
- _sceneState = null
+ _stateInContent = null
}
- private fun removeNodeFromSceneState() {
- sceneState.nodes.remove(this)
+ private fun removeNodeFromContentState() {
+ stateInContent.nodes.remove(this)
}
fun update(
layoutImpl: SceneTransitionLayoutImpl,
currentTransitions: List<TransitionState.Transition>,
- scene: Scene,
+ content: Content,
key: ElementKey,
) {
- check(layoutImpl == this.layoutImpl && scene == this.scene)
+ check(layoutImpl == this.layoutImpl && content == this.content)
this.currentTransitions = currentTransitions
- removeNodeFromSceneState()
+ removeNodeFromContentState()
val prevElement = this.element
- val prevSceneState = this.sceneState
+ val prevElementState = this.stateInContent
this.key = key
- updateElementAndSceneValues()
+ updateElementAndContentValues()
- addNodeToSceneState()
- maybePruneMaps(layoutImpl, prevElement, prevSceneState)
+ addNodeToContentState()
+ maybePruneMaps(layoutImpl, prevElement, prevElementState)
}
override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean {
@@ -262,15 +265,15 @@
check(isLookingAhead)
return measurable.measure(constraints).run {
- // Update the size this element has in this scene when idle.
- sceneState.targetSize = size()
+ // Update the size this element has in this content when idle.
+ stateInContent.targetSize = size()
layout(width, height) {
// Update the offset (relative to the SceneTransitionLayout) this element has in
- // this scene when idle.
+ // this content when idle.
coordinates?.let { coords ->
with(layoutImpl.lookaheadScope) {
- sceneState.targetOffset =
+ stateInContent.targetOffset =
lookaheadScopeCoordinates.localLookaheadPositionOf(coords)
}
}
@@ -287,22 +290,22 @@
val transition = elementTransition(layoutImpl, element, transitions)
// If this element is not supposed to be laid out now, either because it is not part of any
- // ongoing transition or the other scene of its transition is overscrolling, then lay out
+ // ongoing transition or the other content of its transition is overscrolling, then lay out
// the element normally and don't place it.
val overscrollScene = transition?.currentOverscrollSpec?.scene
- val isOtherSceneOverscrolling = overscrollScene != null && overscrollScene != scene.key
+ val isOtherSceneOverscrolling = overscrollScene != null && overscrollScene != content.key
val isNotPartOfAnyOngoingTransitions = transitions.isNotEmpty() && transition == null
if (isNotPartOfAnyOngoingTransitions || isOtherSceneOverscrolling) {
recursivelyClearPlacementValues()
- sceneState.lastSize = Element.SizeUnspecified
+ stateInContent.lastSize = Element.SizeUnspecified
val placeable = measurable.measure(constraints)
return layout(placeable.width, placeable.height) { /* Do not place */ }
}
val placeable =
- measure(layoutImpl, element, transition, sceneState, measurable, constraints)
- sceneState.lastSize = placeable.size()
+ measure(layoutImpl, element, transition, stateInContent, measurable, constraints)
+ stateInContent.lastSize = placeable.size()
return layout(placeable.width, placeable.height) { place(transition, placeable) }
}
@@ -312,12 +315,12 @@
) {
with(layoutImpl.lookaheadScope) {
// Update the offset (relative to the SceneTransitionLayout) this element has in this
- // scene when idle.
+ // content when idle.
val coords =
coordinates ?: error("Element ${element.key} does not have any coordinates")
- // No need to place the element in this scene if we don't want to draw it anyways.
- if (!shouldPlaceElement(layoutImpl, scene.key, element, transition)) {
+ // No need to place the element in this content if we don't want to draw it anyways.
+ if (!shouldPlaceElement(layoutImpl, content.key, element, transition)) {
recursivelyClearPlacementValues()
return
}
@@ -326,10 +329,10 @@
val targetOffset =
computeValue(
layoutImpl,
- sceneState,
+ stateInContent,
element,
transition,
- sceneValue = { it.targetOffset },
+ contentValue = { it.targetOffset },
transformation = { it.offset },
currentValue = { currentOffset },
isSpecified = { it != Offset.Unspecified },
@@ -343,17 +346,17 @@
value = targetOffset,
unspecifiedValue = Offset.Unspecified,
zeroValue = Offset.Zero,
- getValueBeforeInterruption = { sceneState.offsetBeforeInterruption },
- setValueBeforeInterruption = { sceneState.offsetBeforeInterruption = it },
- getInterruptionDelta = { sceneState.offsetInterruptionDelta },
+ getValueBeforeInterruption = { stateInContent.offsetBeforeInterruption },
+ setValueBeforeInterruption = { stateInContent.offsetBeforeInterruption = it },
+ getInterruptionDelta = { stateInContent.offsetInterruptionDelta },
setInterruptionDelta = { delta ->
setPlacementInterruptionDelta(
element = element,
- sceneState = sceneState,
+ stateInContent = stateInContent,
transition = transition,
delta = delta,
- setter = { sceneState, delta ->
- sceneState.offsetInterruptionDelta = delta
+ setter = { stateInContent, delta ->
+ stateInContent.offsetInterruptionDelta = delta
},
)
},
@@ -361,14 +364,15 @@
add = { a, b, bProgress -> a + b * bProgress },
)
- sceneState.lastOffset = interruptedOffset
+ stateInContent.lastOffset = interruptedOffset
val offset = (interruptedOffset - currentOffset).round()
if (
- isElementOpaque(scene, element, transition) &&
- interruptedAlpha(layoutImpl, element, transition, sceneState, alpha = 1f) == 1f
+ isElementOpaque(content, element, transition) &&
+ interruptedAlpha(layoutImpl, element, transition, stateInContent, alpha = 1f) ==
+ 1f
) {
- sceneState.lastAlpha = 1f
+ stateInContent.lastAlpha = 1f
// TODO(b/291071158): Call placeWithLayer() if offset != IntOffset.Zero and size is
// not animated once b/305195729 is fixed. Test that drawing is not invalidated in
@@ -387,11 +391,11 @@
}
val transition = elementTransition(layoutImpl, element, currentTransitions)
- if (!shouldPlaceElement(layoutImpl, scene.key, element, transition)) {
+ if (!shouldPlaceElement(layoutImpl, content.key, element, transition)) {
return@placeWithLayer
}
- alpha = elementAlpha(layoutImpl, element, transition, sceneState)
+ alpha = elementAlpha(layoutImpl, element, transition, stateInContent)
compositingStrategy = CompositingStrategy.ModulateAlpha
}
}
@@ -404,24 +408,24 @@
* for the descendants for which approachMeasure() won't be called.
*/
private fun recursivelyClearPlacementValues() {
- fun Element.SceneState.clearLastPlacementValues() {
+ fun Element.State.clearLastPlacementValues() {
lastOffset = Offset.Unspecified
lastScale = Scale.Unspecified
lastAlpha = Element.AlphaUnspecified
}
- sceneState.clearLastPlacementValues()
+ stateInContent.clearLastPlacementValues()
traverseDescendants(ElementTraverseKey) { node ->
- (node as ElementNode)._sceneState?.clearLastPlacementValues()
+ (node as ElementNode)._stateInContent?.clearLastPlacementValues()
TraversableNode.Companion.TraverseDescendantsAction.ContinueTraversal
}
}
override fun ContentDrawScope.draw() {
- element.wasDrawnInAnyScene = true
+ element.wasDrawnInAnyContent = true
val transition = elementTransition(layoutImpl, element, currentTransitions)
- val drawScale = getDrawScale(layoutImpl, element, transition, sceneState)
+ val drawScale = getDrawScale(layoutImpl, element, transition, stateInContent)
if (drawScale == Scale.Default) {
drawContent()
} else {
@@ -441,16 +445,21 @@
private fun maybePruneMaps(
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
- sceneState: Element.SceneState,
+ stateInContent: Element.State,
) {
- // If element is not composed from this scene anymore, remove the scene values. This
+ // If element is not composed in this content anymore, remove the content values. This
// works because [onAttach] is called before [onDetach], so if an element is moved from
// the UI tree we will first add the new code location then remove the old one.
- if (sceneState.nodes.isEmpty() && element.sceneStates[sceneState.scene] == sceneState) {
- element.sceneStates.remove(sceneState.scene)
+ if (
+ stateInContent.nodes.isEmpty() &&
+ element.stateByContent[stateInContent.content] == stateInContent
+ ) {
+ element.stateByContent.remove(stateInContent.content)
- // If the element is not composed in any scene, remove it from the elements map.
- if (element.sceneStates.isEmpty() && layoutImpl.elements[element.key] == element) {
+ // If the element is not composed in any content, remove it from the elements map.
+ if (
+ element.stateByContent.isEmpty() && layoutImpl.elements[element.key] == element
+ ) {
layoutImpl.elements.remove(element.key)
}
}
@@ -460,7 +469,7 @@
/**
* The transition that we should consider for [element]. This is the last transition where one of
- * its scenes contains the element.
+ * its contents contains the element.
*/
private fun elementTransition(
layoutImpl: SceneTransitionLayoutImpl,
@@ -469,7 +478,8 @@
): TransitionState.Transition? {
val transition =
transitions.fastLastOrNull { transition ->
- transition.fromScene in element.sceneStates || transition.toScene in element.sceneStates
+ transition.fromScene in element.stateByContent ||
+ transition.toScene in element.stateByContent
}
val previousTransition = element.lastTransition
@@ -480,7 +490,7 @@
prepareInterruption(layoutImpl, element, transition, previousTransition)
} else if (transition == null && previousTransition != null) {
// The transition was just finished.
- element.sceneStates.values.forEach {
+ element.stateByContent.values.forEach {
it.clearValuesBeforeInterruption()
it.clearInterruptionDeltas()
}
@@ -499,32 +509,32 @@
return
}
- val sceneStates = element.sceneStates
- fun updatedSceneState(key: SceneKey): Element.SceneState? {
- return sceneStates[key]?.also { it.selfUpdateValuesBeforeInterruption() }
+ val stateByContent = element.stateByContent
+ fun updateStateInContent(key: ContentKey): Element.State? {
+ return stateByContent[key]?.also { it.selfUpdateValuesBeforeInterruption() }
}
- val previousFromState = updatedSceneState(previousTransition.fromScene)
- val previousToState = updatedSceneState(previousTransition.toScene)
- val fromState = updatedSceneState(transition.fromScene)
- val toState = updatedSceneState(transition.toScene)
+ val previousFromState = updateStateInContent(previousTransition.fromScene)
+ val previousToState = updateStateInContent(previousTransition.toScene)
+ val fromState = updateStateInContent(transition.fromScene)
+ val toState = updateStateInContent(transition.toScene)
reconcileStates(element, previousTransition)
reconcileStates(element, transition)
- // Remove the interruption values to all scenes but the scene(s) where the element will be
+ // Remove the interruption values to all contents but the content(s) where the element will be
// placed, to make sure that interruption deltas are computed only right after this interruption
// is prepared.
- fun cleanInterruptionValues(sceneState: Element.SceneState) {
- sceneState.sizeInterruptionDelta = IntSize.Zero
- sceneState.offsetInterruptionDelta = Offset.Zero
- sceneState.alphaInterruptionDelta = 0f
- sceneState.scaleInterruptionDelta = Scale.Zero
+ fun cleanInterruptionValues(stateInContent: Element.State) {
+ stateInContent.sizeInterruptionDelta = IntSize.Zero
+ stateInContent.offsetInterruptionDelta = Offset.Zero
+ stateInContent.alphaInterruptionDelta = 0f
+ stateInContent.scaleInterruptionDelta = Scale.Zero
- if (!shouldPlaceElement(layoutImpl, sceneState.scene, element, transition)) {
- sceneState.offsetBeforeInterruption = Offset.Unspecified
- sceneState.alphaBeforeInterruption = Element.AlphaUnspecified
- sceneState.scaleBeforeInterruption = Scale.Unspecified
+ if (!shouldPlaceElement(layoutImpl, stateInContent.content, element, transition)) {
+ stateInContent.offsetBeforeInterruption = Offset.Unspecified
+ stateInContent.alphaBeforeInterruption = Element.AlphaUnspecified
+ stateInContent.scaleBeforeInterruption = Scale.Unspecified
}
}
@@ -542,8 +552,8 @@
element: Element,
transition: TransitionState.Transition,
) {
- val fromSceneState = element.sceneStates[transition.fromScene] ?: return
- val toSceneState = element.sceneStates[transition.toScene] ?: return
+ val fromSceneState = element.stateByContent[transition.fromScene] ?: return
+ val toSceneState = element.stateByContent[transition.toScene] ?: return
if (!isSharedElementEnabled(element.key, transition)) {
return
}
@@ -563,7 +573,7 @@
}
}
-private fun Element.SceneState.selfUpdateValuesBeforeInterruption() {
+private fun Element.State.selfUpdateValuesBeforeInterruption() {
sizeBeforeInterruption = lastSize
if (lastAlpha > 0f) {
@@ -571,7 +581,7 @@
scaleBeforeInterruption = lastScale
alphaBeforeInterruption = lastAlpha
} else {
- // Consider the element as not placed in this scene if it was fully transparent.
+ // Consider the element as not placed in this content if it was fully transparent.
// TODO(b/290930950): Look into using derived state inside place() instead to not even place
// the element at all when alpha == 0f.
offsetBeforeInterruption = Offset.Unspecified
@@ -580,7 +590,7 @@
}
}
-private fun Element.SceneState.updateValuesBeforeInterruption(lastState: Element.SceneState) {
+private fun Element.State.updateValuesBeforeInterruption(lastState: Element.State) {
offsetBeforeInterruption = lastState.offsetBeforeInterruption
sizeBeforeInterruption = lastState.sizeBeforeInterruption
scaleBeforeInterruption = lastState.scaleBeforeInterruption
@@ -589,14 +599,14 @@
clearInterruptionDeltas()
}
-private fun Element.SceneState.clearInterruptionDeltas() {
+private fun Element.State.clearInterruptionDeltas() {
offsetInterruptionDelta = Offset.Zero
sizeInterruptionDelta = IntSize.Zero
scaleInterruptionDelta = Scale.Zero
alphaInterruptionDelta = 0f
}
-private fun Element.SceneState.clearValuesBeforeInterruption() {
+private fun Element.State.clearValuesBeforeInterruption() {
offsetBeforeInterruption = Offset.Unspecified
scaleBeforeInterruption = Scale.Unspecified
alphaBeforeInterruption = Element.AlphaUnspecified
@@ -655,13 +665,13 @@
*/
private inline fun <T> setPlacementInterruptionDelta(
element: Element,
- sceneState: Element.SceneState,
+ stateInContent: Element.State,
transition: TransitionState.Transition?,
delta: T,
- setter: (Element.SceneState, T) -> Unit,
+ setter: (Element.State, T) -> Unit,
) {
- // Set the interruption delta on the current scene.
- setter(sceneState, delta)
+ // Set the interruption delta on the current content.
+ setter(stateInContent, delta)
if (transition == null) {
return
@@ -670,8 +680,9 @@
// If the element is shared, also set the delta on the other scene so that it is used by that
// scene if we start overscrolling it and change the scene where the element is placed.
val otherScene =
- if (sceneState.scene == transition.fromScene) transition.toScene else transition.fromScene
- val otherSceneState = element.sceneStates[otherScene] ?: return
+ if (stateInContent.content == transition.fromScene) transition.toScene
+ else transition.fromScene
+ val otherSceneState = element.stateByContent[otherScene] ?: return
if (isSharedElementEnabled(element.key, transition)) {
setter(otherSceneState, delta)
}
@@ -679,7 +690,7 @@
private fun shouldPlaceElement(
layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
+ content: ContentKey,
element: Element,
transition: TransitionState.Transition?,
): Boolean {
@@ -688,15 +699,16 @@
return true
}
- // Don't place the element in this scene if this scene is not part of the current element
+ // Don't place the element in this content if this content is not part of the current element
// transition.
- if (scene != transition.fromScene && scene != transition.toScene) {
+ if (content != transition.fromScene && content != transition.toScene) {
return false
}
// Place the element if it is not shared.
if (
- transition.fromScene !in element.sceneStates || transition.toScene !in element.sceneStates
+ transition.fromScene !in element.stateByContent ||
+ transition.toScene !in element.stateByContent
) {
return true
}
@@ -708,7 +720,7 @@
return shouldPlaceOrComposeSharedElement(
layoutImpl,
- scene,
+ content,
element.key,
transition,
)
@@ -716,14 +728,14 @@
internal fun shouldPlaceOrComposeSharedElement(
layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
+ content: ContentKey,
element: ElementKey,
transition: TransitionState.Transition,
): Boolean {
// If we are overscrolling, only place/compose the element in the overscrolling scene.
val overscrollScene = transition.currentOverscrollSpec?.scene
if (overscrollScene != null) {
- return scene == overscrollScene
+ return content == overscrollScene
}
val scenePicker = element.scenePicker
@@ -738,7 +750,7 @@
toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex,
) ?: return false
- return pickedScene == scene
+ return pickedScene == content
}
private fun isSharedElementEnabled(
@@ -775,7 +787,7 @@
* placement and we don't want to read the transition progress in that phase.
*/
private fun isElementOpaque(
- scene: Scene,
+ content: Content,
element: Element,
transition: TransitionState.Transition?,
): Boolean {
@@ -785,8 +797,8 @@
val fromScene = transition.fromScene
val toScene = transition.toScene
- val fromState = element.sceneStates[fromScene]
- val toState = element.sceneStates[toScene]
+ val fromState = element.stateByContent[fromScene]
+ val toState = element.stateByContent[toScene]
if (fromState == null && toState == null) {
// TODO(b/311600838): Throw an exception instead once layers of disposed elements are not
@@ -799,7 +811,7 @@
return true
}
- return transition.transformationSpec.transformations(element.key, scene.key).alpha == null
+ return transition.transformationSpec.transformations(element.key, content.key).alpha == null
}
/**
@@ -814,15 +826,15 @@
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
transition: TransitionState.Transition?,
- sceneState: Element.SceneState,
+ stateInContent: Element.State,
): Float {
val alpha =
computeValue(
layoutImpl,
- sceneState,
+ stateInContent,
element,
transition,
- sceneValue = { 1f },
+ contentValue = { 1f },
transformation = { it.alpha },
currentValue = { 1f },
isSpecified = { true },
@@ -832,12 +844,12 @@
// If the element is fading during this transition and that it is drawn for the first time, make
// sure that it doesn't instantly appear on screen.
- if (!element.wasDrawnInAnyScene && alpha > 0f) {
- element.sceneStates.forEach { it.value.alphaBeforeInterruption = 0f }
+ if (!element.wasDrawnInAnyContent && alpha > 0f) {
+ element.stateByContent.forEach { it.value.alphaBeforeInterruption = 0f }
}
- val interruptedAlpha = interruptedAlpha(layoutImpl, element, transition, sceneState, alpha)
- sceneState.lastAlpha = interruptedAlpha
+ val interruptedAlpha = interruptedAlpha(layoutImpl, element, transition, stateInContent, alpha)
+ stateInContent.lastAlpha = interruptedAlpha
return interruptedAlpha
}
@@ -845,7 +857,7 @@
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
transition: TransitionState.Transition?,
- sceneState: Element.SceneState,
+ stateInContent: Element.State,
alpha: Float,
): Float {
return computeInterruptedValue(
@@ -854,16 +866,16 @@
value = alpha,
unspecifiedValue = Element.AlphaUnspecified,
zeroValue = 0f,
- getValueBeforeInterruption = { sceneState.alphaBeforeInterruption },
- setValueBeforeInterruption = { sceneState.alphaBeforeInterruption = it },
- getInterruptionDelta = { sceneState.alphaInterruptionDelta },
+ getValueBeforeInterruption = { stateInContent.alphaBeforeInterruption },
+ setValueBeforeInterruption = { stateInContent.alphaBeforeInterruption = it },
+ getInterruptionDelta = { stateInContent.alphaInterruptionDelta },
setInterruptionDelta = { delta ->
setPlacementInterruptionDelta(
element = element,
- sceneState = sceneState,
+ stateInContent = stateInContent,
transition = transition,
delta = delta,
- setter = { sceneState, delta -> sceneState.alphaInterruptionDelta = delta },
+ setter = { stateInContent, delta -> stateInContent.alphaInterruptionDelta = delta },
)
},
diff = { a, b -> a - b },
@@ -875,7 +887,7 @@
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
transition: TransitionState.Transition?,
- sceneState: Element.SceneState,
+ stateInContent: Element.State,
measurable: Measurable,
constraints: Constraints,
): Placeable {
@@ -887,10 +899,10 @@
val targetSize =
computeValue(
layoutImpl,
- sceneState,
+ stateInContent,
element,
transition,
- sceneValue = { it.targetSize },
+ contentValue = { it.targetSize },
transformation = { it.size },
currentValue = { measurable.measure(constraints).also { maybePlaceable = it }.size() },
isSpecified = { it != Element.SizeUnspecified },
@@ -900,8 +912,8 @@
// The measurable was already measured, so we can't take interruptions into account here given
// that we are not allowed to measure the same measurable twice.
maybePlaceable?.let { placeable ->
- sceneState.sizeBeforeInterruption = Element.SizeUnspecified
- sceneState.sizeInterruptionDelta = IntSize.Zero
+ stateInContent.sizeBeforeInterruption = Element.SizeUnspecified
+ stateInContent.sizeInterruptionDelta = IntSize.Zero
return placeable
}
@@ -912,10 +924,10 @@
value = targetSize,
unspecifiedValue = Element.SizeUnspecified,
zeroValue = IntSize.Zero,
- getValueBeforeInterruption = { sceneState.sizeBeforeInterruption },
- setValueBeforeInterruption = { sceneState.sizeBeforeInterruption = it },
- getInterruptionDelta = { sceneState.sizeInterruptionDelta },
- setInterruptionDelta = { sceneState.sizeInterruptionDelta = it },
+ getValueBeforeInterruption = { stateInContent.sizeBeforeInterruption },
+ setValueBeforeInterruption = { stateInContent.sizeBeforeInterruption = it },
+ getInterruptionDelta = { stateInContent.sizeInterruptionDelta },
+ setInterruptionDelta = { stateInContent.sizeInterruptionDelta = it },
diff = { a, b -> IntSize(a.width - b.width, a.height - b.height) },
add = { a, b, bProgress ->
IntSize(
@@ -939,15 +951,15 @@
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
transition: TransitionState.Transition?,
- sceneState: Element.SceneState,
+ stateInContent: Element.State,
): Scale {
val scale =
computeValue(
layoutImpl,
- sceneState,
+ stateInContent,
element,
transition,
- sceneValue = { Scale.Default },
+ contentValue = { Scale.Default },
transformation = { it.drawScale },
currentValue = { Scale.Default },
isSpecified = { true },
@@ -965,16 +977,18 @@
value = scale,
unspecifiedValue = Scale.Unspecified,
zeroValue = Scale.Zero,
- getValueBeforeInterruption = { sceneState.scaleBeforeInterruption },
- setValueBeforeInterruption = { sceneState.scaleBeforeInterruption = it },
- getInterruptionDelta = { sceneState.scaleInterruptionDelta },
+ getValueBeforeInterruption = { stateInContent.scaleBeforeInterruption },
+ setValueBeforeInterruption = { stateInContent.scaleBeforeInterruption = it },
+ getInterruptionDelta = { stateInContent.scaleInterruptionDelta },
setInterruptionDelta = { delta ->
setPlacementInterruptionDelta(
element = element,
- sceneState = sceneState,
+ stateInContent = stateInContent,
transition = transition,
delta = delta,
- setter = { sceneState, delta -> sceneState.scaleInterruptionDelta = delta },
+ setter = { stateInContent, delta ->
+ stateInContent.scaleInterruptionDelta = delta
+ },
)
},
diff = { a, b ->
@@ -1003,7 +1017,7 @@
}
)
- sceneState.lastScale = interruptedScale
+ stateInContent.lastScale = interruptedScale
return interruptedScale
}
@@ -1015,11 +1029,11 @@
* Measurable.
*
* @param layoutImpl the [SceneTransitionLayoutImpl] associated to [element].
- * @param currentSceneState the scene state of the scene for which we are computing the value. Note
- * that during interruptions, this could be the state of a scene that is neither
+ * @param currentContentState the content state of the content for which we are computing the value.
+ * Note that during interruptions, this could be the state of a content that is neither
* [transition.toScene] nor [transition.fromScene].
* @param element the element being animated.
- * @param sceneValue the value being animated.
+ * @param contentValue the value being animated.
* @param transformation the transformation associated to the value being animated.
* @param currentValue the value that would be used if it is not transformed. Note that this is
* different than [idleValue] even if the value is not transformed directly because it could be
@@ -1030,10 +1044,10 @@
*/
private inline fun <T> computeValue(
layoutImpl: SceneTransitionLayoutImpl,
- currentSceneState: Element.SceneState,
+ currentContentState: Element.State,
element: Element,
transition: TransitionState.Transition?,
- sceneValue: (Element.SceneState) -> T,
+ contentValue: (Element.State) -> T,
transformation: (ElementTransformations) -> PropertyTransformation<T>?,
currentValue: () -> T,
isSpecified: (T) -> Boolean,
@@ -1050,16 +1064,16 @@
val fromScene = transition.fromScene
val toScene = transition.toScene
- val fromState = element.sceneStates[fromScene]
- val toState = element.sceneStates[toScene]
+ val fromState = element.stateByContent[fromScene]
+ val toState = element.stateByContent[toScene]
if (fromState == null && toState == null) {
// TODO(b/311600838): Throw an exception instead once layers of disposed elements are not
// run anymore.
- return sceneValue(currentSceneState)
+ return contentValue(currentContentState)
}
- val currentScene = currentSceneState.scene
+ val currentScene = currentContentState.content
if (transition is TransitionState.HasOverscrollProperties) {
val overscroll = transition.currentOverscrollSpec
if (overscroll?.scene == currentScene) {
@@ -1067,7 +1081,7 @@
overscroll.transformationSpec.transformations(element.key, currentScene)
val propertySpec = transformation(elementSpec) ?: return currentValue()
val overscrollState = checkNotNull(if (currentScene == toScene) toState else fromState)
- val idleValue = sceneValue(overscrollState)
+ val idleValue = contentValue(overscrollState)
val targetValue =
propertySpec.transform(
layoutImpl,
@@ -1102,8 +1116,8 @@
// elements follow the finger direction.
val isSharedElement = fromState != null && toState != null
if (isSharedElement && isSharedElementEnabled(element.key, transition)) {
- val start = sceneValue(fromState!!)
- val end = sceneValue(toState!!)
+ val start = contentValue(fromState!!)
+ val end = contentValue(toState!!)
// TODO(b/316901148): Remove checks to isSpecified() once the lookahead pass runs for all
// nodes before the intermediate layout pass.
@@ -1117,7 +1131,7 @@
// Get the transformed value, i.e. the target value at the beginning (for entering elements) or
// end (for leaving elements) of the transition.
- val sceneState =
+ val contentState =
checkNotNull(
when {
isSharedElement && currentScene == fromScene -> fromState
@@ -1129,26 +1143,26 @@
// The scene for which we compute the transformation. Note that this is not necessarily
// [currentScene] because [currentScene] could be a different scene than the transition
// fromScene or toScene during interruptions.
- val scene = sceneState.scene
+ val content = contentState.content
val transformation =
- transformation(transition.transformationSpec.transformations(element.key, scene))
+ transformation(transition.transformationSpec.transformations(element.key, content))
val previewTransformation =
transition.previewTransformationSpec?.let {
- transformation(it.transformations(element.key, scene))
+ transformation(it.transformations(element.key, content))
}
if (previewTransformation != null) {
val isInPreviewStage = transition.isInPreviewStage
- val idleValue = sceneValue(sceneState)
- val isEntering = scene == toScene
+ val idleValue = contentValue(contentState)
+ val isEntering = content == toScene
val previewTargetValue =
previewTransformation.transform(
layoutImpl,
- scene,
+ content,
element,
- sceneState,
+ contentState,
transition,
idleValue,
)
@@ -1156,9 +1170,9 @@
val targetValueOrNull =
transformation?.transform(
layoutImpl,
- scene,
+ content,
element,
- sceneState,
+ contentState,
transition,
idleValue,
)
@@ -1226,13 +1240,13 @@
return currentValue()
}
- val idleValue = sceneValue(sceneState)
+ val idleValue = contentValue(contentState)
val targetValue =
transformation.transform(
layoutImpl,
- scene,
+ content,
element,
- sceneState,
+ contentState,
transition,
idleValue,
)
@@ -1248,7 +1262,7 @@
val rangeProgress = transformation.range?.progress(progress) ?: progress
// Interpolate between the value at rest and the value before entering/after leaving.
- val isEntering = scene == toScene
+ val isEntering = content == toScene
return if (isEntering) {
lerp(targetValue, idleValue, rangeProgress)
} else {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
index 98dbb67..ca68c25 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
@@ -18,20 +18,23 @@
/** An interface to match one or more elements. */
interface ElementMatcher {
- /** Whether the element with key [key] in scene [scene] matches this matcher. */
- fun matches(key: ElementKey, scene: SceneKey): Boolean
+ /** Whether the element with key [key] in scene [content] matches this matcher. */
+ fun matches(key: ElementKey, content: ContentKey): Boolean
}
/**
- * Returns an [ElementMatcher] that matches elements in [scene] also matching [this]
+ * Returns an [ElementMatcher] that matches elements in [content] also matching [this]
* [ElementMatcher].
*/
-fun ElementMatcher.inScene(scene: SceneKey): ElementMatcher {
+fun ElementMatcher.inContent(content: ContentKey): ElementMatcher {
val delegate = this
- val matcherScene = scene
+ val matcherScene = content
return object : ElementMatcher {
- override fun matches(key: ElementKey, scene: SceneKey): Boolean {
- return scene == matcherScene && delegate.matches(key, scene)
+ override fun matches(key: ElementKey, content: ContentKey): Boolean {
+ return content == matcherScene && delegate.matches(key, content)
}
}
}
+
+@Deprecated("Use inContent() instead", replaceWith = ReplaceWith("inContent(scene)"))
+fun ElementMatcher.inScene(scene: SceneKey) = inContent(scene)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index 9770399..a9edf0a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -40,15 +40,20 @@
}
}
+/** The key for a content (scene or overlay). */
+sealed class ContentKey(debugName: String, identity: Any) : Key(debugName, identity) {
+ @VisibleForTesting
+ // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
+ // access internal members.
+ abstract val testTag: String
+}
+
/** Key for a scene. */
class SceneKey(
debugName: String,
identity: Any = Object(),
-) : Key(debugName, identity) {
- @VisibleForTesting
- // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
- // access internal members.
- val testTag: String = "scene:$debugName"
+) : ContentKey(debugName, identity) {
+ override val testTag: String = "scene:$debugName"
/** The unique [ElementKey] identifying this scene's root element. */
val rootElementKey = ElementKey(debugName, identity)
@@ -74,7 +79,7 @@
// access internal members.
val testTag: String = "element:$debugName"
- override fun matches(key: ElementKey, scene: SceneKey): Boolean {
+ override fun matches(key: ElementKey, content: ContentKey): Boolean {
return key == this
}
@@ -86,7 +91,7 @@
/** Matches any element whose [key identity][ElementKey.identity] matches [predicate]. */
fun withIdentity(predicate: (Any) -> Boolean): ElementMatcher {
return object : ElementMatcher {
- override fun matches(key: ElementKey, scene: SceneKey): Boolean {
+ override fun matches(key: ElementKey, content: ContentKey): Boolean {
return predicate(key.identity)
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 32eadde..e556f6f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -27,21 +27,22 @@
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastLastOrNull
+import com.android.compose.animation.scene.content.Content
@Composable
internal fun Element(
layoutImpl: SceneTransitionLayoutImpl,
- scene: Scene,
+ sceneOrOverlay: Content,
key: ElementKey,
modifier: Modifier,
content: @Composable ElementScope<ElementContentScope>.() -> Unit,
) {
- Box(modifier.element(layoutImpl, scene, key)) {
- val sceneScope = scene.scope
+ Box(modifier.element(layoutImpl, sceneOrOverlay, key)) {
+ val contentScope = sceneOrOverlay.scope
val boxScope = this
val elementScope =
- remember(layoutImpl, key, scene, sceneScope, boxScope) {
- ElementScopeImpl(layoutImpl, key, scene, sceneScope, boxScope)
+ remember(layoutImpl, key, sceneOrOverlay, contentScope, boxScope) {
+ ElementScopeImpl(layoutImpl, key, sceneOrOverlay, contentScope, boxScope)
}
content(elementScope)
@@ -51,17 +52,17 @@
@Composable
internal fun MovableElement(
layoutImpl: SceneTransitionLayoutImpl,
- scene: Scene,
+ sceneOrOverlay: Content,
key: ElementKey,
modifier: Modifier,
content: @Composable ElementScope<MovableElementContentScope>.() -> Unit,
) {
- Box(modifier.element(layoutImpl, scene, key)) {
- val sceneScope = scene.scope
+ Box(modifier.element(layoutImpl, sceneOrOverlay, key)) {
+ val contentScope = sceneOrOverlay.scope
val boxScope = this
val elementScope =
- remember(layoutImpl, key, scene, sceneScope, boxScope) {
- MovableElementScopeImpl(layoutImpl, key, scene, sceneScope, boxScope)
+ remember(layoutImpl, key, sceneOrOverlay, contentScope, boxScope) {
+ MovableElementScopeImpl(layoutImpl, key, sceneOrOverlay, contentScope, boxScope)
}
content(elementScope)
@@ -71,7 +72,7 @@
private abstract class BaseElementScope<ContentScope>(
private val layoutImpl: SceneTransitionLayoutImpl,
private val element: ElementKey,
- private val scene: Scene,
+ private val sceneOrOverlay: Content,
) : ElementScope<ContentScope> {
@Composable
override fun <T> animateElementValueAsState(
@@ -82,7 +83,7 @@
): AnimatedState<T> {
return animateSharedValueAsState(
layoutImpl,
- scene.key,
+ sceneOrOverlay.key,
element,
key,
value,
@@ -95,12 +96,12 @@
private class ElementScopeImpl(
layoutImpl: SceneTransitionLayoutImpl,
element: ElementKey,
- scene: Scene,
- private val sceneScope: SceneScope,
+ content: Content,
+ private val delegateContentScope: ContentScope,
private val boxScope: BoxScope,
-) : BaseElementScope<ElementContentScope>(layoutImpl, element, scene) {
+) : BaseElementScope<ElementContentScope>(layoutImpl, element, content) {
private val contentScope =
- object : ElementContentScope, SceneScope by sceneScope, BoxScope by boxScope {}
+ object : ElementContentScope, ContentScope by delegateContentScope, BoxScope by boxScope {}
@Composable
override fun content(content: @Composable ElementContentScope.() -> Unit) {
@@ -111,12 +112,15 @@
private class MovableElementScopeImpl(
private val layoutImpl: SceneTransitionLayoutImpl,
private val element: ElementKey,
- private val scene: Scene,
- private val sceneScope: BaseSceneScope,
+ private val content: Content,
+ private val baseContentScope: BaseContentScope,
private val boxScope: BoxScope,
-) : BaseElementScope<MovableElementContentScope>(layoutImpl, element, scene) {
+) : BaseElementScope<MovableElementContentScope>(layoutImpl, element, content) {
private val contentScope =
- object : MovableElementContentScope, BaseSceneScope by sceneScope, BoxScope by boxScope {}
+ object :
+ MovableElementContentScope,
+ BaseContentScope by baseContentScope,
+ BoxScope by boxScope {}
@Composable
override fun content(content: @Composable MovableElementContentScope.() -> Unit) {
@@ -126,9 +130,10 @@
// during the transition.
// TODO(b/317026105): Use derivedStateOf only if the scene picker reads the progress in its
// logic.
+ val contentKey = this@MovableElementScopeImpl.content.key
val shouldComposeMovableElement by
- remember(layoutImpl, scene.key, element) {
- derivedStateOf { shouldComposeMovableElement(layoutImpl, scene.key, element) }
+ remember(layoutImpl, contentKey, element) {
+ derivedStateOf { shouldComposeMovableElement(layoutImpl, contentKey, element) }
}
if (shouldComposeMovableElement) {
@@ -152,7 +157,7 @@
val size =
placeholderContentSize(
layoutImpl,
- scene.key,
+ contentKey,
layoutImpl.elements.getValue(element),
)
layout(size.width, size.height) {}
@@ -163,7 +168,7 @@
private fun shouldComposeMovableElement(
layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
+ content: ContentKey,
element: ElementKey,
): Boolean {
val transitions = layoutImpl.state.currentTransitions
@@ -171,7 +176,7 @@
// If we are idle, there is only one [scene] that is composed so we can compose our
// movable content here. We still check that [scene] is equal to the current idle scene, to
// make sure we only compose it there.
- return layoutImpl.state.transitionState.currentScene == scene
+ return layoutImpl.state.transitionState.currentScene == content
}
// The current transition for this element is the last transition in which either fromScene or
@@ -189,7 +194,7 @@
// Always compose movable elements in the scene picked by their scene picker.
return shouldPlaceOrComposeSharedElement(
layoutImpl,
- scene,
+ content,
element,
transition,
)
@@ -201,12 +206,12 @@
*/
private fun placeholderContentSize(
layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
+ content: ContentKey,
element: Element,
): IntSize {
// If the content of the movable element was already composed in this scene before, use that
// target size.
- val targetValueInScene = element.sceneStates.getValue(scene).targetSize
+ val targetValueInScene = element.stateByContent.getValue(content).targetSize
if (targetValueInScene != Element.SizeUnspecified) {
return targetValueInScene
}
@@ -219,8 +224,9 @@
// doesn't change between scenes.
// TODO(b/317026105): Provide a way to give a hint size/content for cases where this is not
// true.
- val otherScene = if (transition.fromScene == scene) transition.toScene else transition.fromScene
- val targetValueInOtherScene = element.sceneStates[otherScene]?.targetSize
+ val otherScene =
+ if (transition.fromScene == content) transition.toScene else transition.fromScene
+ val targetValueInOtherScene = element.stateByContent[otherScene]?.targetSize
if (targetValueInOtherScene != null && targetValueInOtherScene != Element.SizeUnspecified) {
return targetValueInOtherScene
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 2fc4526..3401af8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -34,7 +34,6 @@
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
-import com.android.compose.animation.scene.UserAction.Resolved
/**
* [SceneTransitionLayout] is a container that automatically animates its content whenever its state
@@ -85,7 +84,7 @@
fun scene(
key: SceneKey,
userActions: Map<UserAction, UserActionResult> = emptyMap(),
- content: @Composable SceneScope.() -> Unit,
+ content: @Composable ContentScope.() -> Unit,
)
}
@@ -118,25 +117,25 @@
@Stable
@ElementDsl
-interface BaseSceneScope : ElementStateScope {
- /** The key of this scene. */
- val sceneKey: SceneKey
+interface BaseContentScope : ElementStateScope {
+ /** The key of this content. */
+ val contentKey: ContentKey
- /** The state of the [SceneTransitionLayout] in which this scene is contained. */
+ /** The state of the [SceneTransitionLayout] in which this content is contained. */
val layoutState: SceneTransitionLayoutState
/**
* Tag an element identified by [key].
*
* Tagging an element will allow you to reference that element when defining transitions, so
- * that the element can be transformed and animated when the scene transitions in or out.
+ * that the element can be transformed and animated when the content transitions in or out.
*
- * Additionally, this [key] will be used to detect elements that are shared between scenes to
+ * Additionally, this [key] will be used to detect elements that are shared between contents to
* automatically interpolate their size and offset. If you need to animate shared element values
- * (i.e. values associated to this element that change depending on which scene it is composed
+ * (i.e. values associated to this element that change depending on which content it is composed
* in), use [Element] instead.
*
- * Note that shared elements tagged using this function will be duplicated in each scene they
+ * Note that shared elements tagged using this function will be duplicated in each content they
* are part of, so any **internal** state (e.g. state created using `remember {
* mutableStateOf(...) }`) will be lost. If you need to preserve internal state, you should use
* [MovableElement] instead.
@@ -150,7 +149,7 @@
* Create an element identified by [key].
*
* Similar to [element], this creates an element that will be automatically shared when present
- * in multiple scenes and that can be transformed during transitions, the same way that
+ * in multiple contents and that can be transformed during transitions, the same way that
* [element] does.
*
* The only difference with [element] is that the provided [ElementScope] allows you to
@@ -177,7 +176,7 @@
* Create a *movable* element identified by [key].
*
* Similar to [Element], this creates an element that will be automatically shared when present
- * in multiple scenes and that can be transformed during transitions, and you can also use the
+ * in multiple contents and that can be transformed during transitions, and you can also use the
* provided [ElementScope] to [animate element values][ElementScope.animateElementValueAsState].
*
* The important difference with [element] and [Element] is that this element
@@ -232,24 +231,26 @@
fun Modifier.noResizeDuringTransitions(): Modifier
}
+typealias SceneScope = ContentScope
+
@Stable
@ElementDsl
-interface SceneScope : BaseSceneScope {
+interface ContentScope : BaseContentScope {
/**
- * Animate some value at the scene level.
+ * Animate some value at the content level.
*
* @param value the value of this shared value in the current scene.
* @param key the key of this shared value.
* @param type the [SharedValueType] of this animated value.
* @param canOverflow whether this value can overflow past the values it is interpolated
* between, for instance because the transition is animated using a bouncy spring.
- * @see animateSceneIntAsState
- * @see animateSceneFloatAsState
- * @see animateSceneDpAsState
- * @see animateSceneColorAsState
+ * @see animateContentIntAsState
+ * @see animateContentFloatAsState
+ * @see animateContentDpAsState
+ * @see animateContentColorAsState
*/
@Composable
- fun <T> animateSceneValueAsState(
+ fun <T> animateContentValueAsState(
value: T,
key: ValueKey,
type: SharedValueType<T, *>,
@@ -259,7 +260,7 @@
/**
* The type of a shared value animated using [ElementScope.animateElementValueAsState] or
- * [SceneScope.animateSceneValueAsState].
+ * [ContentScope.animateContentValueAsState].
*/
@Stable
interface SharedValueType<T, Delta> {
@@ -321,8 +322,9 @@
* The exact same scope as [androidx.compose.foundation.layout.BoxScope].
*
* We can't reuse BoxScope directly because of the @LayoutScopeMarker annotation on it, which would
- * prevent us from calling Modifier.element() and other methods of [SceneScope] inside any Box {} in
- * the [content][ElementScope.content] of a [SceneScope.Element] or a [SceneScope.MovableElement].
+ * prevent us from calling Modifier.element() and other methods of [ContentScope] inside any Box {}
+ * in the [content][ElementScope.content] of a [ContentScope.Element] or a
+ * [ContentScope.MovableElement].
*/
@Stable
@ElementDsl
@@ -335,16 +337,16 @@
}
/** The scope for "normal" (not movable) elements. */
-@Stable @ElementDsl interface ElementContentScope : SceneScope, ElementBoxScope
+@Stable @ElementDsl interface ElementContentScope : ContentScope, ElementBoxScope
/**
* The scope for the content of movable elements.
*
- * Note that it extends [BaseSceneScope] and not [SceneScope] because movable elements should not
- * call [SceneScope.animateSceneValueAsState], given that their content is not composed in all
- * scenes.
+ * Note that it extends [BaseContentScope] and not [ContentScope] because movable elements should
+ * not call [ContentScope.animateContentValueAsState], given that their content is not composed in
+ * all scenes.
*/
-@Stable @ElementDsl interface MovableElementContentScope : BaseSceneScope, ElementBoxScope
+@Stable @ElementDsl interface MovableElementContentScope : BaseContentScope, ElementBoxScope
/** An action performed by the user. */
sealed class UserAction {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 32db0b7..062d553 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -36,6 +36,8 @@
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachReversed
+import com.android.compose.animation.scene.content.Content
+import com.android.compose.animation.scene.content.Scene
import com.android.compose.ui.util.lerp
import kotlinx.coroutines.CoroutineScope
@@ -84,7 +86,7 @@
/**
* The different values of a shared value keyed by a a [ValueKey] and the different elements and
- * scenes it is associated to.
+ * contents it is associated to.
*/
private var _sharedValues: MutableMap<ValueKey, MutableMap<ElementKey?, SharedValue<*, *>>>? =
null
@@ -149,6 +151,12 @@
return scenes[key] ?: error("Scene $key is not configured")
}
+ internal fun content(key: ContentKey): Content {
+ return when (key) {
+ is SceneKey -> scene(key)
+ }
+ }
+
internal fun updateScenes(
builder: SceneTransitionLayoutScope.() -> Unit,
layoutDirection: LayoutDirection,
@@ -164,7 +172,7 @@
override fun scene(
key: SceneKey,
userActions: Map<UserAction, UserActionResult>,
- content: @Composable SceneScope.() -> Unit,
+ content: @Composable ContentScope.() -> Unit,
) {
scenesToRemove.remove(key)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 06b093d..cfa4c70 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -302,18 +302,18 @@
override val distance: UserActionDistance?,
override val transformations: List<Transformation>,
) : TransformationSpec {
- private val cache = mutableMapOf<ElementKey, MutableMap<SceneKey, ElementTransformations>>()
+ private val cache = mutableMapOf<ElementKey, MutableMap<ContentKey, ElementTransformations>>()
- internal fun transformations(element: ElementKey, scene: SceneKey): ElementTransformations {
+ internal fun transformations(element: ElementKey, content: ContentKey): ElementTransformations {
return cache
.getOrPut(element) { mutableMapOf() }
- .getOrPut(scene) { computeTransformations(element, scene) }
+ .getOrPut(content) { computeTransformations(element, content) }
}
/** Filter [transformations] to compute the [ElementTransformations] of [element]. */
private fun computeTransformations(
element: ElementKey,
- scene: SceneKey,
+ content: ContentKey,
): ElementTransformations {
var shared: SharedElementTransformation? = null
var offset: PropertyTransformation<Offset>? = null
@@ -351,7 +351,7 @@
}
transformations.fastForEach { transformation ->
- if (!transformation.matcher.matches(element, scene)) {
+ if (!transformation.matcher.matches(element, content)) {
return@fastForEach
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index a2118b2..f062146 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -31,6 +31,7 @@
import androidx.compose.ui.node.TraversableNode
import androidx.compose.ui.node.findNearestAncestor
import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.content.Scene
/**
* Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 3a87d41..06be86d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -239,10 +239,11 @@
* should not be drawn or composed in neither [transition.fromScene] nor [transition.toScene],
* return `null`.
*
- * Important: For [MovableElements][SceneScope.MovableElement], this scene picker will *always*
- * be used during transitions to decide whether we should compose that element in a given scene
- * or not. Therefore, you should make sure that the returned [SceneKey] contains the movable
- * element, otherwise that element will not be composed in any scene during the transition.
+ * Important: For [MovableElements][ContentScope.MovableElement], this scene picker will
+ * *always* be used during transitions to decide whether we should compose that element in a
+ * given scene or not. Therefore, you should make sure that the returned [SceneKey] contains the
+ * movable element, otherwise that element will not be composed in any scene during the
+ * transition.
*/
fun sceneDuringTransition(
element: ElementKey,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
index b7abb33..0f66804 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
@@ -23,13 +23,13 @@
private val layoutImpl: SceneTransitionLayoutImpl,
) : ElementStateScope {
override fun ElementKey.targetSize(scene: SceneKey): IntSize? {
- return layoutImpl.elements[this]?.sceneStates?.get(scene)?.targetSize.takeIf {
+ return layoutImpl.elements[this]?.stateByContent?.get(scene)?.targetSize.takeIf {
it != Element.SizeUnspecified
}
}
override fun ElementKey.targetOffset(scene: SceneKey): Offset? {
- return layoutImpl.elements[this]?.sceneStates?.get(scene)?.targetOffset.takeIf {
+ return layoutImpl.elements[this]?.stateByContent?.get(scene)?.targetOffset.takeIf {
it != Offset.Unspecified
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
similarity index 63%
rename from packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index a49f1af..492d211 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.compose.animation.scene
+package com.android.compose.animation.scene.content
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
@@ -29,24 +29,44 @@
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.zIndex
+import com.android.compose.animation.scene.AnimatedState
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementContentScope
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementScope
+import com.android.compose.animation.scene.ElementStateScope
+import com.android.compose.animation.scene.MovableElement
+import com.android.compose.animation.scene.MovableElementContentScope
+import com.android.compose.animation.scene.NestedScrollBehavior
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+import com.android.compose.animation.scene.SharedValueType
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.compose.animation.scene.ValueKey
+import com.android.compose.animation.scene.animateSharedValueAsState
+import com.android.compose.animation.scene.element
import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions
+import com.android.compose.animation.scene.nestedScrollToScene
-/** A scene in a [SceneTransitionLayout]. */
+/** A content defined in a [SceneTransitionLayout], i.e. a scene or an overlay. */
@Stable
-internal class Scene(
- val key: SceneKey,
- layoutImpl: SceneTransitionLayoutImpl,
- content: @Composable SceneScope.() -> Unit,
+internal sealed class Content(
+ open val key: ContentKey,
+ val layoutImpl: SceneTransitionLayoutImpl,
+ content: @Composable ContentScope.() -> Unit,
actions: Map<UserAction.Resolved, UserActionResult>,
zIndex: Float,
) {
- internal val scope = SceneScopeImpl(layoutImpl, this)
+ internal val scope = ContentScopeImpl(layoutImpl, content = this)
var content by mutableStateOf(content)
- private var _userActions by mutableStateOf(checkValid(actions))
var zIndex by mutableFloatStateOf(zIndex)
var targetSize by mutableStateOf(IntSize.Zero)
+ private var _userActions by mutableStateOf(checkValid(actions))
var userActions
get() = _userActions
set(value) {
@@ -59,8 +79,8 @@
userActions.forEach { (action, result) ->
if (key == result.toScene) {
error(
- "Transition to the same scene is not supported. Scene $key, action $action," +
- " result $result"
+ "Transition to the same content (scene/overlay) is not supported. Content " +
+ "$key, action $action, result $result"
)
}
}
@@ -73,7 +93,7 @@
modifier
.zIndex(zIndex)
.approachLayout(
- isMeasurementApproachInProgress = { scope.layoutState.isTransitioning() }
+ isMeasurementApproachInProgress = { layoutImpl.state.isTransitioning() }
) { measurable, constraints ->
targetSize = lookaheadSize
val placeable = measurable.measure(constraints)
@@ -84,21 +104,19 @@
scope.content()
}
}
-
- override fun toString(): String {
- return "Scene(key=$key)"
- }
}
-internal class SceneScopeImpl(
+internal class ContentScopeImpl(
private val layoutImpl: SceneTransitionLayoutImpl,
- private val scene: Scene,
-) : SceneScope, ElementStateScope by layoutImpl.elementStateScope {
- override val sceneKey: SceneKey = scene.key
+ private val content: Content,
+) : ContentScope, ElementStateScope by layoutImpl.elementStateScope {
+ override val contentKey: ContentKey
+ get() = content.key
+
override val layoutState: SceneTransitionLayoutState = layoutImpl.state
override fun Modifier.element(key: ElementKey): Modifier {
- return element(layoutImpl, scene, key)
+ return element(layoutImpl, content, key)
}
@Composable
@@ -107,7 +125,7 @@
modifier: Modifier,
content: @Composable (ElementScope<ElementContentScope>.() -> Unit)
) {
- Element(layoutImpl, scene, key, modifier, content)
+ Element(layoutImpl, this@ContentScopeImpl.content, key, modifier, content)
}
@Composable
@@ -116,19 +134,19 @@
modifier: Modifier,
content: @Composable (ElementScope<MovableElementContentScope>.() -> Unit)
) {
- MovableElement(layoutImpl, scene, key, modifier, content)
+ MovableElement(layoutImpl, this@ContentScopeImpl.content, key, modifier, content)
}
@Composable
- override fun <T> animateSceneValueAsState(
+ override fun <T> animateContentValueAsState(
value: T,
key: ValueKey,
type: SharedValueType<T, *>,
- canOverflow: Boolean
+ canOverflow: Boolean,
): AnimatedState<T> {
return animateSharedValueAsState(
layoutImpl = layoutImpl,
- scene = scene.key,
+ content = content.key,
element = null,
key = key,
value = value,
@@ -141,27 +159,29 @@
leftBehavior: NestedScrollBehavior,
rightBehavior: NestedScrollBehavior,
isExternalOverscrollGesture: () -> Boolean,
- ): Modifier =
- nestedScrollToScene(
+ ): Modifier {
+ return nestedScrollToScene(
layoutImpl = layoutImpl,
orientation = Orientation.Horizontal,
topOrLeftBehavior = leftBehavior,
bottomOrRightBehavior = rightBehavior,
isExternalOverscrollGesture = isExternalOverscrollGesture,
)
+ }
override fun Modifier.verticalNestedScrollToScene(
topBehavior: NestedScrollBehavior,
bottomBehavior: NestedScrollBehavior,
isExternalOverscrollGesture: () -> Boolean,
- ): Modifier =
- nestedScrollToScene(
+ ): Modifier {
+ return nestedScrollToScene(
layoutImpl = layoutImpl,
orientation = Orientation.Vertical,
topOrLeftBehavior = topBehavior,
bottomOrRightBehavior = bottomBehavior,
isExternalOverscrollGesture = isExternalOverscrollGesture,
)
+ }
override fun Modifier.noResizeDuringTransitions(): Modifier {
return noResizeDuringTransitions(layoutState = layoutImpl.state)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt
new file mode 100644
index 0000000..4a7a94d
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.content
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+
+/** A scene defined in a [SceneTransitionLayout]. */
+@Stable
+internal class Scene(
+ override val key: SceneKey,
+ layoutImpl: SceneTransitionLayoutImpl,
+ content: @Composable ContentScope.() -> Unit,
+ actions: Map<UserAction.Resolved, UserActionResult>,
+ zIndex: Float,
+) : Content(key, layoutImpl, content, actions, zIndex) {
+ override fun toString(): String {
+ return "Scene(key=$key)"
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
index 73ee451..65d4d2d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene.transformation
import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
@@ -33,15 +34,15 @@
) : PropertyTransformation<IntSize> {
override fun transform(
layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
+ content: ContentKey,
element: Element,
- sceneState: Element.SceneState,
+ sceneState: Element.State,
transition: TransitionState.Transition,
value: IntSize,
): IntSize {
fun anchorSizeIn(scene: SceneKey): IntSize {
val size =
- layoutImpl.elements[anchor]?.sceneStates?.get(scene)?.targetSize?.takeIf {
+ layoutImpl.elements[anchor]?.stateByContent?.get(scene)?.targetSize?.takeIf {
it != Element.SizeUnspecified
}
?: throwMissingAnchorException(
@@ -59,7 +60,7 @@
// This simple implementation assumes that the size of [element] is the same as the size of
// the [anchor] in [scene], so simply transform to the size of the anchor in the other
// scene.
- return if (scene == transition.fromScene) {
+ return if (content == transition.fromScene) {
anchorSizeIn(transition.toScene)
} else {
anchorSizeIn(transition.fromScene)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
index 70dca4c..8d7e1c9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
@@ -18,6 +18,7 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.isSpecified
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
@@ -32,9 +33,9 @@
) : PropertyTransformation<Offset> {
override fun transform(
layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
+ content: ContentKey,
element: Element,
- sceneState: Element.SceneState,
+ sceneState: Element.State,
transition: TransitionState.Transition,
value: Offset,
): Offset {
@@ -48,7 +49,7 @@
val anchor = layoutImpl.elements[anchor] ?: throwException(scene = null)
fun anchorOffsetIn(scene: SceneKey): Offset? {
- return anchor.sceneStates[scene]?.targetOffset?.takeIf { it.isSpecified }
+ return anchor.stateByContent[scene]?.targetOffset?.takeIf { it.isSpecified }
}
// [element] will move the same amount as [anchor] does.
@@ -60,7 +61,7 @@
anchorOffsetIn(transition.toScene) ?: throwException(transition.toScene)
val offset = anchorToOffset - anchorFromOffset
- return if (scene == transition.toScene) {
+ return if (content == transition.toScene) {
Offset(
value.x - offset.x,
value.y - offset.y,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
index 98c2dd3..f010c3b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
@@ -17,10 +17,10 @@
package com.android.compose.animation.scene.transformation
import androidx.compose.ui.geometry.Offset
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.Scale
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.TransitionState
@@ -37,9 +37,9 @@
override fun transform(
layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
+ content: ContentKey,
element: Element,
- sceneState: Element.SceneState,
+ sceneState: Element.State,
transition: TransitionState.Transition,
value: Scale,
): Scale {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index 7daefd0..dfce997 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -17,10 +17,10 @@
package com.android.compose.animation.scene.transformation
import androidx.compose.ui.geometry.Offset
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.TransitionState
@@ -32,13 +32,13 @@
) : PropertyTransformation<Offset> {
override fun transform(
layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
+ content: ContentKey,
element: Element,
- sceneState: Element.SceneState,
+ sceneState: Element.State,
transition: TransitionState.Transition,
value: Offset
): Offset {
- val sceneSize = layoutImpl.scene(scene).targetSize
+ val sceneSize = layoutImpl.content(content).targetSize
val elementSize = sceneState.targetSize
if (elementSize == Element.SizeUnspecified) {
return value
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
index ada814e..c1bb017 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
@@ -16,9 +16,9 @@
package com.android.compose.animation.scene.transformation
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.TransitionState
@@ -28,9 +28,9 @@
) : PropertyTransformation<Float> {
override fun transform(
layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
+ content: ContentKey,
element: Element,
- sceneState: Element.SceneState,
+ sceneState: Element.State,
transition: TransitionState.Transition,
value: Float
): Float {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
index dca8f85..5adbf7e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
@@ -17,9 +17,9 @@
package com.android.compose.animation.scene.transformation
import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.TransitionState
import kotlin.math.roundToInt
@@ -35,9 +35,9 @@
) : PropertyTransformation<IntSize> {
override fun transform(
layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
+ content: ContentKey,
element: Element,
- sceneState: Element.SceneState,
+ sceneState: Element.State,
transition: TransitionState.Transition,
value: IntSize,
): IntSize {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 7be9ce1..24b7194 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -19,9 +19,9 @@
import androidx.compose.ui.util.fastCoerceAtLeast
import androidx.compose.ui.util.fastCoerceAtMost
import androidx.compose.ui.util.fastCoerceIn
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.TransitionState
@@ -61,9 +61,9 @@
// to these internal classes.
fun transform(
layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
+ content: ContentKey,
element: Element,
- sceneState: Element.SceneState,
+ sceneState: Element.State,
transition: TransitionState.Transition,
value: T,
): T
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index f066511..123756a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -19,10 +19,10 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.OverscrollScope
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.TransitionState
@@ -33,9 +33,9 @@
) : PropertyTransformation<Offset> {
override fun transform(
layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
+ content: ContentKey,
element: Element,
- sceneState: Element.SceneState,
+ sceneState: Element.State,
transition: TransitionState.Transition,
value: Offset,
): Offset {
@@ -55,9 +55,9 @@
) : PropertyTransformation<Offset> {
override fun transform(
layoutImpl: SceneTransitionLayoutImpl,
- scene: SceneKey,
+ content: ContentKey,
element: Element,
- sceneState: Element.SceneState,
+ sceneState: Element.State,
transition: TransitionState.Transition,
value: Offset,
): Offset {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index 8e35988..ae3169b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -57,7 +57,7 @@
minHeight() < currentHeight && currentHeight < maxHeight()
},
canScrollOnFling = true,
- onStart = { /* do nothing */},
+ onStart = { /* do nothing */ },
onScroll = { offsetAvailable ->
val currentHeight = height()
val amountConsumed =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index ac11d30..228f7ba 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -38,7 +38,7 @@
private val canStartPreScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
private val canStartPostFling: (velocityAvailable: Velocity) -> Boolean,
- private val canContinueScroll: () -> Boolean,
+ private val canContinueScroll: (source: NestedScrollSource) -> Boolean,
private val canScrollOnFling: Boolean,
private val onStart: (offsetAvailable: Offset) -> Unit,
private val onScroll: (offsetAvailable: Offset) -> Offset,
@@ -61,7 +61,7 @@
if (
isPriorityMode ||
- (source == NestedScrollSource.Fling && !canScrollOnFling) ||
+ (source == NestedScrollSource.SideEffect && !canScrollOnFling) ||
!canStartPostScroll(available, offsetBeforeStart)
) {
// The priority mode cannot start so we won't consume the available offset.
@@ -73,7 +73,7 @@
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
if (!isPriorityMode) {
- if (source != NestedScrollSource.Fling || canScrollOnFling) {
+ if (source == NestedScrollSource.UserInput || canScrollOnFling) {
if (canStartPreScroll(available, offsetScrolledBeforePriorityMode)) {
return onPriorityStart(available)
}
@@ -84,7 +84,7 @@
return Offset.Zero
}
- if (!canContinueScroll()) {
+ if (!canContinueScroll(source)) {
// Step 3a: We have lost priority and we no longer need to intercept scroll events.
onPriorityStop(velocity = Velocity.Zero)
@@ -170,7 +170,7 @@
canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
canStartPostFling: (velocityAvailable: Float) -> Boolean,
- canContinueScroll: () -> Boolean,
+ canContinueScroll: (source: NestedScrollSource) -> Boolean,
canScrollOnFling: Boolean,
onStart: (offsetAvailable: Float) -> Unit,
onScroll: (offsetAvailable: Float) -> Float,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index a7889e2..0f33303 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -67,7 +67,7 @@
}
@Composable
- private fun SceneScope.Foo(
+ private fun ContentScope.Foo(
targetValues: Values,
onCurrentValueChanged: (Values) -> Unit,
) {
@@ -87,7 +87,7 @@
}
@Composable
- private fun SceneScope.MovableFoo(
+ private fun ContentScope.MovableFoo(
targetValues: Values,
onCurrentValueChanged: (Values) -> Unit,
) {
@@ -105,14 +105,14 @@
}
@Composable
- private fun SceneScope.SceneValues(
+ private fun ContentScope.SceneValues(
targetValues: Values,
onCurrentValueChanged: (Values) -> Unit,
) {
- val int by animateSceneIntAsState(targetValues.int, key = TestValues.Value1)
- val float by animateSceneFloatAsState(targetValues.float, key = TestValues.Value2)
- val dp by animateSceneDpAsState(targetValues.dp, key = TestValues.Value3)
- val color by animateSceneColorAsState(targetValues.color, key = TestValues.Value4)
+ val int by animateContentIntAsState(targetValues.int, key = TestValues.Value1)
+ val float by animateContentFloatAsState(targetValues.float, key = TestValues.Value2)
+ val dp by animateContentDpAsState(targetValues.dp, key = TestValues.Value3)
+ val color by animateContentColorAsState(targetValues.color, key = TestValues.Value4)
LaunchedEffect(Unit) {
snapshotFlow { Values(int, float, dp, color) }.collect(onCurrentValueChanged)
@@ -292,7 +292,7 @@
fun readingAnimatedStateValueDuringCompositionThrows() {
assertThrows(IllegalStateException::class.java) {
rule.testTransition(
- fromSceneContent = { animateSceneIntAsState(0, TestValues.Value1).value },
+ fromSceneContent = { animateContentIntAsState(0, TestValues.Value1).value },
toSceneContent = {},
transition = {},
) {}
@@ -302,21 +302,21 @@
@Test
fun readingAnimatedStateValueDuringCompositionIsStillPossible() {
@Composable
- fun SceneScope.SceneValuesDuringComposition(
+ fun ContentScope.SceneValuesDuringComposition(
targetValues: Values,
onCurrentValueChanged: (Values) -> Unit,
) {
val int by
- animateSceneIntAsState(targetValues.int, key = TestValues.Value1)
+ animateContentIntAsState(targetValues.int, key = TestValues.Value1)
.unsafeCompositionState(targetValues.int)
val float by
- animateSceneFloatAsState(targetValues.float, key = TestValues.Value2)
+ animateContentFloatAsState(targetValues.float, key = TestValues.Value2)
.unsafeCompositionState(targetValues.float)
val dp by
- animateSceneDpAsState(targetValues.dp, key = TestValues.Value3)
+ animateContentDpAsState(targetValues.dp, key = TestValues.Value3)
.unsafeCompositionState(targetValues.dp)
val color by
- animateSceneColorAsState(targetValues.color, key = TestValues.Value4)
+ animateContentColorAsState(targetValues.color, key = TestValues.Value4)
.unsafeCompositionState(targetValues.color)
val values = Values(int, float, dp, color)
@@ -397,14 +397,14 @@
val foo = ValueKey("foo")
val bar = ValueKey("bar")
- val lastValues = mutableMapOf<ValueKey, MutableMap<SceneKey, Float>>()
+ val lastValues = mutableMapOf<ValueKey, MutableMap<ContentKey, Float>>()
@Composable
- fun SceneScope.animateFloat(value: Float, key: ValueKey) {
- val animatedValue = animateSceneFloatAsState(value, key)
+ fun ContentScope.animateFloat(value: Float, key: ValueKey) {
+ val animatedValue = animateContentFloatAsState(value, key)
LaunchedEffect(animatedValue) {
snapshotFlow { animatedValue.value }
- .collect { lastValues.getOrPut(key) { mutableMapOf() }[sceneKey] = it }
+ .collect { lastValues.getOrPut(key) { mutableMapOf() }[contentKey] = it }
}
}
@@ -458,13 +458,13 @@
}
val key = ValueKey("foo")
- val lastValues = mutableMapOf<SceneKey, Float>()
+ val lastValues = mutableMapOf<ContentKey, Float>()
@Composable
- fun SceneScope.animateFloat(value: Float, key: ValueKey) {
- val animatedValue = animateSceneFloatAsState(value, key)
+ fun ContentScope.animateFloat(value: Float, key: ValueKey) {
+ val animatedValue = animateContentFloatAsState(value, key)
LaunchedEffect(animatedValue) {
- snapshotFlow { animatedValue.value }.collect { lastValues[sceneKey] = it }
+ snapshotFlow { animatedValue.value }.collect { lastValues[contentKey] = it }
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 1d9e9b7..329257e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -86,7 +86,7 @@
@get:Rule val rule = createComposeRule()
@Composable
- private fun SceneScope.Element(
+ private fun ContentScope.Element(
key: ElementKey,
size: Dp,
offset: Dp,
@@ -380,7 +380,7 @@
assertThat(layoutImpl.elements.keys).containsExactly(key)
val element = layoutImpl.elements.getValue(key)
- assertThat(element.sceneStates.keys).containsExactly(SceneB)
+ assertThat(element.stateByContent.keys).containsExactly(SceneB)
// Scene C, state 0: the same element is reused.
rule.runOnUiThread { state.setTargetScene(SceneC, coroutineScope) }
@@ -389,13 +389,13 @@
assertThat(layoutImpl.elements.keys).containsExactly(key)
assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
- assertThat(element.sceneStates.keys).containsExactly(SceneC)
+ assertThat(element.stateByContent.keys).containsExactly(SceneC)
// Scene C, state 1: the element is removed from the map.
sceneCState = 1
rule.waitForIdle()
- assertThat(element.sceneStates).isEmpty()
+ assertThat(element.stateByContent).isEmpty()
assertThat(layoutImpl.elements).isEmpty()
}
@@ -405,7 +405,7 @@
assertThrows(IllegalStateException::class.java) {
rule.setContent {
- TestSceneScope {
+ TestContentScope {
Column {
Box(Modifier.element(key))
Box(Modifier.element(key))
@@ -421,7 +421,7 @@
assertThrows(IllegalStateException::class.java) {
rule.setContent {
- TestSceneScope {
+ TestContentScope {
Column {
val childModifier = Modifier.element(key)
Box(childModifier)
@@ -439,7 +439,7 @@
assertThrows(IllegalStateException::class.java) {
var nElements by mutableStateOf(1)
rule.setContent {
- TestSceneScope {
+ TestContentScope {
Column {
val childModifier = Modifier.element(key)
repeat(nElements) { Box(childModifier) }
@@ -457,7 +457,7 @@
assertThrows(IllegalStateException::class.java) {
var key by mutableStateOf(TestElements.Foo)
rule.setContent {
- TestSceneScope {
+ TestContentScope {
Column {
Box(Modifier.element(key))
Box(Modifier.element(TestElements.Bar))
@@ -491,7 +491,7 @@
// There is only Foo in the elements map.
assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Foo)
val fooElement = layoutImpl.elements.getValue(TestElements.Foo)
- assertThat(fooElement.sceneStates.keys).containsExactly(SceneA)
+ assertThat(fooElement.stateByContent.keys).containsExactly(SceneA)
key = TestElements.Bar
@@ -499,8 +499,8 @@
rule.waitForIdle()
assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Bar)
val barElement = layoutImpl.elements.getValue(TestElements.Bar)
- assertThat(barElement.sceneStates.keys).containsExactly(SceneA)
- assertThat(fooElement.sceneStates).isEmpty()
+ assertThat(barElement.stateByContent.keys).containsExactly(SceneA)
+ assertThat(fooElement.stateByContent).isEmpty()
}
@Test
@@ -553,7 +553,7 @@
// There is only Foo in the elements map.
assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Foo)
val element = layoutImpl.elements.getValue(TestElements.Foo)
- val sceneValues = element.sceneStates
+ val sceneValues = element.stateByContent
assertThat(sceneValues.keys).containsExactly(SceneA)
// Get the ElementModifier node that should be reused later on when coming back to this
@@ -576,7 +576,7 @@
assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Foo)
val newElement = layoutImpl.elements.getValue(TestElements.Foo)
- val newSceneValues = newElement.sceneStates
+ val newSceneValues = newElement.stateByContent
assertThat(newElement).isNotEqualTo(element)
assertThat(newSceneValues).isNotEqualTo(sceneValues)
assertThat(newSceneValues.keys).containsExactly(SceneA)
@@ -677,7 +677,7 @@
modifier = Modifier.size(layoutWidth, layoutHeight)
) {
scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
- animateSceneFloatAsState(
+ animateContentFloatAsState(
value = animatedFloatRange.start,
key = TestValues.Value1,
false
@@ -686,7 +686,7 @@
}
scene(SceneB) {
val animatedFloat by
- animateSceneFloatAsState(
+ animateContentFloatAsState(
value = animatedFloatRange.endInclusive,
key = TestValues.Value1,
canOverflow = false
@@ -1215,15 +1215,15 @@
}
val layoutSize = DpSize(200.dp, 100.dp)
- val lastValues = mutableMapOf<SceneKey, Float>()
+ val lastValues = mutableMapOf<ContentKey, Float>()
@Composable
- fun SceneScope.Foo(size: Dp, value: Float, modifier: Modifier = Modifier) {
- val sceneKey = this.sceneKey
+ fun ContentScope.Foo(size: Dp, value: Float, modifier: Modifier = Modifier) {
+ val contentKey = this.contentKey
Element(TestElements.Foo, modifier.size(size)) {
val animatedValue = animateElementFloatAsState(value, TestValues.Value1)
LaunchedEffect(animatedValue) {
- snapshotFlow { animatedValue.value }.collect { lastValues[sceneKey] = it }
+ snapshotFlow { animatedValue.value }.collect { lastValues[contentKey] = it }
}
}
}
@@ -1388,8 +1388,8 @@
// The interruption values should be unspecified and deltas should be set to zero.
val foo = layoutImpl.elements.getValue(TestElements.Foo)
- assertThat(foo.sceneStates.keys).containsExactly(SceneC)
- val stateInC = foo.sceneStates.getValue(SceneC)
+ assertThat(foo.stateByContent.keys).containsExactly(SceneC)
+ val stateInC = foo.stateByContent.getValue(SceneC)
assertThat(stateInC.offsetBeforeInterruption).isEqualTo(Offset.Unspecified)
assertThat(stateInC.sizeBeforeInterruption).isEqualTo(Element.SizeUnspecified)
assertThat(stateInC.scaleBeforeInterruption).isEqualTo(Scale.Unspecified)
@@ -1423,7 +1423,7 @@
}
@Composable
- fun SceneScope.Foo(modifier: Modifier = Modifier) {
+ fun ContentScope.Foo(modifier: Modifier = Modifier) {
Box(modifier.element(TestElements.Foo).size(fooSize))
}
@@ -1542,8 +1542,8 @@
assertThat(layoutImpl.elements).containsKey(TestElements.Foo)
val foo = layoutImpl.elements.getValue(TestElements.Foo)
- assertThat(foo.sceneStates).containsKey(SceneB)
- val bState = foo.sceneStates.getValue(SceneB)
+ assertThat(foo.stateByContent).containsKey(SceneB)
+ val bState = foo.stateByContent.getValue(SceneB)
assertThat(bState.targetSize).isNotEqualTo(Element.SizeUnspecified)
assertThat(bState.targetOffset).isNotEqualTo(Offset.Unspecified)
@@ -1583,9 +1583,9 @@
rule.waitForIdle()
val foo = checkNotNull(layoutImpl.elements[TestElements.Foo])
- assertThat(foo.sceneStates[SceneA]).isNull()
+ assertThat(foo.stateByContent[SceneA]).isNull()
- val fooInB = foo.sceneStates[SceneB]
+ val fooInB = foo.stateByContent[SceneB]
assertThat(fooInB).isNotNull()
assertThat(fooInB!!.lastAlpha).isEqualTo(0.5f)
@@ -1599,7 +1599,7 @@
state.startTransition(transition(from = SceneB, to = SceneC, progress = { 0.3f }))
}
rule.waitForIdle()
- val fooInC = foo.sceneStates[SceneC]
+ val fooInC = foo.stateByContent[SceneC]
assertThat(fooInC).isNotNull()
assertThat(fooInC!!.lastAlpha).isEqualTo(1f)
assertThat(fooInB.lastAlpha).isEqualTo(Element.AlphaUnspecified)
@@ -1645,7 +1645,7 @@
rule.waitForIdle()
// Alpha of Foo should be 0f at interruption progress 100%.
- val fooInB = layoutImpl.elements.getValue(TestElements.Foo).sceneStates.getValue(SceneB)
+ val fooInB = layoutImpl.elements.getValue(TestElements.Foo).stateByContent.getValue(SceneB)
assertThat(fooInB.lastAlpha).isEqualTo(0f)
// Alpha of Foo should be 0.6f at interruption progress 0%.
@@ -1673,7 +1673,7 @@
}
@Composable
- fun SceneScope.Foo() {
+ fun ContentScope.Foo() {
Box(Modifier.element(TestElements.Foo).size(10.dp))
}
@@ -1724,7 +1724,7 @@
val fooInB = "fooInB"
@Composable
- fun SceneScope.MovableFoo(text: String, modifier: Modifier = Modifier) {
+ fun ContentScope.MovableFoo(text: String, modifier: Modifier = Modifier) {
MovableElement(TestElements.Foo, modifier) { content { Text(text) } }
}
@@ -1773,7 +1773,7 @@
}
@Composable
- fun SceneScope.SceneWithFoo(offset: DpOffset, modifier: Modifier = Modifier) {
+ fun ContentScope.SceneWithFoo(offset: DpOffset, modifier: Modifier = Modifier) {
Box(modifier.fillMaxSize()) {
Box(Modifier.offset(offset.x, offset.y).element(TestElements.Foo).size(100.dp))
}
@@ -1856,7 +1856,7 @@
val state = rule.runOnIdle { MutableSceneTransitionLayoutStateImpl(SceneA) }
@Composable
- fun SceneScope.NestedFooBar() {
+ fun ContentScope.NestedFooBar() {
Box(Modifier.element(TestElements.Foo)) {
Box(Modifier.element(TestElements.Bar).size(10.dp))
}
@@ -1881,13 +1881,13 @@
val foo = layoutImpl.elements.getValue(TestElements.Foo)
val bar = layoutImpl.elements.getValue(TestElements.Bar)
- assertThat(foo.sceneStates).containsKey(SceneA)
- assertThat(bar.sceneStates).containsKey(SceneA)
- assertThat(foo.sceneStates).doesNotContainKey(SceneB)
- assertThat(bar.sceneStates).doesNotContainKey(SceneB)
+ assertThat(foo.stateByContent).containsKey(SceneA)
+ assertThat(bar.stateByContent).containsKey(SceneA)
+ assertThat(foo.stateByContent).doesNotContainKey(SceneB)
+ assertThat(bar.stateByContent).doesNotContainKey(SceneB)
- val fooInA = foo.sceneStates.getValue(SceneA)
- val barInA = bar.sceneStates.getValue(SceneA)
+ val fooInA = foo.stateByContent.getValue(SceneA)
+ val barInA = bar.stateByContent.getValue(SceneA)
assertThat(fooInA.lastOffset).isNotEqualTo(Offset.Unspecified)
assertThat(fooInA.lastAlpha).isNotEqualTo(Element.AlphaUnspecified)
assertThat(fooInA.lastScale).isNotEqualTo(Scale.Unspecified)
@@ -1903,11 +1903,11 @@
rule.onNode(isElement(TestElements.Foo, SceneB)).assertIsDisplayed()
rule.onNode(isElement(TestElements.Bar, SceneB)).assertIsDisplayed()
- assertThat(foo.sceneStates).containsKey(SceneB)
- assertThat(bar.sceneStates).containsKey(SceneB)
+ assertThat(foo.stateByContent).containsKey(SceneB)
+ assertThat(bar.stateByContent).containsKey(SceneB)
- val fooInB = foo.sceneStates.getValue(SceneB)
- val barInB = bar.sceneStates.getValue(SceneB)
+ val fooInB = foo.stateByContent.getValue(SceneB)
+ val barInB = bar.stateByContent.getValue(SceneB)
assertThat(fooInA.lastOffset).isEqualTo(Offset.Unspecified)
assertThat(fooInA.lastAlpha).isEqualTo(Element.AlphaUnspecified)
assertThat(fooInA.lastScale).isEqualTo(Scale.Unspecified)
@@ -1938,8 +1938,8 @@
}
@Composable
- fun SceneScope.Foo() {
- Box(Modifier.testTag("fooParentIn${sceneKey.debugName}")) {
+ fun ContentScope.Foo() {
+ Box(Modifier.testTag("fooParentIn${contentKey.debugName}")) {
Box(Modifier.element(TestElements.Foo).size(20.dp))
}
}
@@ -1973,7 +1973,7 @@
val state = rule.runOnIdle { MutableSceneTransitionLayoutStateImpl(SceneA) }
@Composable
- fun SceneScope.Foo(offset: Dp) {
+ fun ContentScope.Foo(offset: Dp) {
Box(Modifier.fillMaxSize()) {
Box(Modifier.offset(offset, offset).element(TestElements.Foo).size(20.dp))
}
@@ -2041,7 +2041,7 @@
}
@Composable
- fun SceneScope.Foo() {
+ fun ContentScope.Foo() {
Box(Modifier.element(TestElements.Foo).size(10.dp))
}
@@ -2062,7 +2062,11 @@
rule.waitForIdle()
assertThat(
- layoutImpl.elements.getValue(TestElements.Foo).sceneStates.getValue(SceneB).lastSize
+ layoutImpl.elements
+ .getValue(TestElements.Foo)
+ .stateByContent
+ .getValue(SceneB)
+ .lastSize
)
.isEqualTo(Element.SizeUnspecified)
}
@@ -2078,8 +2082,8 @@
// In A => B, Foo is not shared and first fades out from A then fades in
// B.
sharedElement(TestElements.Foo, enabled = false)
- fractionRange(end = 0.5f) { fade(TestElements.Foo.inScene(SceneA)) }
- fractionRange(start = 0.5f) { fade(TestElements.Foo.inScene(SceneB)) }
+ fractionRange(end = 0.5f) { fade(TestElements.Foo.inContent(SceneA)) }
+ fractionRange(start = 0.5f) { fade(TestElements.Foo.inContent(SceneB)) }
}
from(SceneB, to = SceneA) {
@@ -2091,7 +2095,7 @@
}
@Composable
- fun SceneScope.Foo(modifier: Modifier = Modifier) {
+ fun ContentScope.Foo(modifier: Modifier = Modifier) {
Box(modifier.element(TestElements.Foo).size(10.dp))
}
@@ -2149,7 +2153,7 @@
val state = rule.runOnIdle { MutableSceneTransitionLayoutStateImpl(SceneA) }
@Composable
- fun SceneScope.Foo(modifier: Modifier = Modifier) {
+ fun ContentScope.Foo(modifier: Modifier = Modifier) {
Box(modifier.element(TestElements.Foo).size(10.dp))
}
@@ -2216,7 +2220,7 @@
// verify that preview transition for exiting elements is halfway played from
// current-scene-value -> preview-target-value
- val exiting1InB = layoutImpl.elements.getValue(exiting1).sceneStates.getValue(SceneB)
+ val exiting1InB = layoutImpl.elements.getValue(exiting1).stateByContent.getValue(SceneB)
// e.g. exiting1 is half scaled...
assertThat(exiting1InB.lastScale).isEqualTo(Scale(0.9f, 0.9f, Offset.Unspecified))
// ...and exiting2 is halfway translated from 0.dp to 20.dp...
@@ -2228,7 +2232,7 @@
// verify that preview transition for entering elements is halfway played from
// preview-target-value -> transition-target-value (or target-scene-value if no
// transition-target-value defined).
- val entering1InA = layoutImpl.elements.getValue(entering1).sceneStates.getValue(SceneA)
+ val entering1InA = layoutImpl.elements.getValue(entering1).stateByContent.getValue(SceneA)
// e.g. entering1 is half scaled between 0f and 0.5f -> 0.25f...
assertThat(entering1InA.lastScale).isEqualTo(Scale(0.25f, 0.25f, Offset.Unspecified))
// ...and entering2 is half way translated between 30.dp and 0.dp
@@ -2272,7 +2276,7 @@
// verify that exiting elements remain in the preview-end state if no further transition is
// defined for them in the second stage
- val exiting1InB = layoutImpl.elements.getValue(exiting1).sceneStates.getValue(SceneB)
+ val exiting1InB = layoutImpl.elements.getValue(exiting1).stateByContent.getValue(SceneB)
// i.e. exiting1 remains half scaled
assertThat(exiting1InB.lastScale).isEqualTo(Scale(0.9f, 0.9f, Offset.Unspecified))
// in case there is an additional transition defined for the second stage, verify that the
@@ -2286,7 +2290,7 @@
rule.onNode(isElement(exiting3)).assertSizeIsEqualTo(90.dp, 90.dp)
// verify that entering elements animate seamlessly to their target state
- val entering1InA = layoutImpl.elements.getValue(entering1).sceneStates.getValue(SceneA)
+ val entering1InA = layoutImpl.elements.getValue(entering1).stateByContent.getValue(SceneA)
// e.g. entering1, which was scaled from 0f to 0.25f during the preview phase, should now be
// half way scaled between 0.25f and its target-state of 1f -> 0.625f
assertThat(entering1InA.lastScale).isEqualTo(Scale(0.625f, 0.625f, Offset.Unspecified))
@@ -2318,7 +2322,7 @@
}
@Composable
- fun SceneScope.Foo(elementKey: ElementKey) {
+ fun ContentScope.Foo(elementKey: ElementKey) {
Box(Modifier.element(elementKey).size(100.dp))
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 9523896..821cc29 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -62,7 +62,7 @@
}
@Composable
- private fun SceneScope.MovableCounter(key: ElementKey, modifier: Modifier) {
+ private fun ContentScope.MovableCounter(key: ElementKey, modifier: Modifier) {
MovableElement(key, modifier) { content { Counter() } }
}
@@ -264,7 +264,7 @@
@Test
fun movableElementContentIsRecomposedIfContentParametersChange() {
@Composable
- fun SceneScope.MovableFoo(text: String, modifier: Modifier = Modifier) {
+ fun ContentScope.MovableFoo(text: String, modifier: Modifier = Modifier) {
MovableElement(TestElements.Foo, modifier) { content { Text(text) } }
}
@@ -298,7 +298,7 @@
@Test
fun elementScopeExtendsBoxScope() {
rule.setContent {
- TestSceneScope {
+ TestContentScope {
Element(TestElements.Foo, Modifier.size(200.dp)) {
content {
Box(Modifier.testTag("bottomEnd").align(Alignment.BottomEnd))
@@ -315,7 +315,7 @@
@Test
fun movableElementScopeExtendsBoxScope() {
rule.setContent {
- TestSceneScope {
+ TestContentScope {
MovableElement(TestElements.Foo, Modifier.size(200.dp)) {
content {
Box(Modifier.testTag("bottomEnd").align(Alignment.BottomEnd))
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
index 311a580..9ebc426 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
@@ -48,7 +48,7 @@
private val layoutHeight = 400.dp
private fun setup2ScenesAndScrollTouchSlop(
- modifierSceneA: @Composable SceneScope.() -> Modifier = { Modifier },
+ modifierSceneA: @Composable ContentScope.() -> Modifier = { Modifier },
): MutableSceneTransitionLayoutState {
val state =
rule.runOnUiThread {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 1ec1079..32f3bac 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -129,7 +129,7 @@
}
@Composable
- private fun SceneScope.SharedFoo(size: Dp, childOffset: Dp, modifier: Modifier = Modifier) {
+ private fun ContentScope.SharedFoo(size: Dp, childOffset: Dp, modifier: Modifier = Modifier) {
Element(TestElements.Foo, modifier.size(size).background(Color.Red)) {
// Offset the single child of Foo by some animated shared offset.
val offset by animateElementDpAsState(childOffset, TestValues.Value1)
@@ -479,14 +479,14 @@
fun sceneKeyInScope() {
val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
- var keyInA: SceneKey? = null
- var keyInB: SceneKey? = null
- var keyInC: SceneKey? = null
+ var keyInA: ContentKey? = null
+ var keyInB: ContentKey? = null
+ var keyInC: ContentKey? = null
rule.setContent {
SceneTransitionLayout(state) {
- scene(SceneA) { keyInA = sceneKey }
- scene(SceneB) { keyInB = sceneKey }
- scene(SceneC) { keyInC = sceneKey }
+ scene(SceneA) { keyInA = contentKey }
+ scene(SceneB) { keyInB = contentKey }
+ scene(SceneC) { keyInC = contentKey }
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
index 6233608..c9f71da 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
@@ -25,7 +25,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.TestElements
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.TransitionRecordingSpec
@@ -108,8 +108,8 @@
}
private fun assertBarSizeMatchesGolden(
- fromSceneContent: @Composable SceneScope.() -> Unit,
- toSceneContent: @Composable SceneScope.() -> Unit,
+ fromSceneContent: @Composable ContentScope.() -> Unit,
+ toSceneContent: @Composable ContentScope.() -> Unit,
transition: TransitionBuilder.() -> Unit,
) {
val recordingSpec =
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
index 8001f41..00acb13 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
@@ -31,7 +31,7 @@
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TestElements
import com.android.compose.animation.scene.TestScenes
-import com.android.compose.animation.scene.inScene
+import com.android.compose.animation.scene.inContent
import com.android.compose.animation.scene.testTransition
import com.android.compose.test.assertSizeIsEqualTo
import org.junit.Rule
@@ -125,10 +125,10 @@
sharedElement(TestElements.Foo, enabled = false)
// In SceneA, Foo leaves to the left edge.
- translate(TestElements.Foo.inScene(TestScenes.SceneA), Edge.Left)
+ translate(TestElements.Foo.inContent(TestScenes.SceneA), Edge.Left)
// In SceneB, Foo comes from the bottom edge.
- translate(TestElements.Foo.inScene(TestScenes.SceneB), Edge.Bottom)
+ translate(TestElements.Foo.inContent(TestScenes.SceneB), Edge.Bottom)
},
) {
before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestSceneScope.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
similarity index 85%
rename from packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestSceneScope.kt
rename to packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
index fbd557f..00adefb 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestSceneScope.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
@@ -20,11 +20,13 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-/** `SceneScope` for tests, which allows a single scene to be drawn in a [SceneTransitionLayout]. */
+/**
+ * [ContentScope] for tests, which allows a single scene to be drawn in a [SceneTransitionLayout].
+ */
@Composable
-fun TestSceneScope(
+fun TestContentScope(
modifier: Modifier = Modifier,
- content: @Composable SceneScope.() -> Unit,
+ content: @Composable ContentScope.() -> Unit,
) {
val currentScene = remember { SceneKey("current") }
val state = remember { MutableSceneTransitionLayoutState(currentScene) }
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index a37d78e..7f26b98 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -18,9 +18,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.test.SemanticsNodeInteraction
@@ -87,8 +85,8 @@
* @sample com.android.compose.animation.scene.transformation.TranslateTest
*/
fun ComposeContentTestRule.testTransition(
- fromSceneContent: @Composable SceneScope.() -> Unit,
- toSceneContent: @Composable SceneScope.() -> Unit,
+ fromSceneContent: @Composable ContentScope.() -> Unit,
+ toSceneContent: @Composable ContentScope.() -> Unit,
transition: TransitionBuilder.() -> Unit,
layoutModifier: Modifier = Modifier,
fromScene: SceneKey = TestScenes.SceneA,
@@ -134,8 +132,8 @@
/** Records the transition between two scenes of [transitionLayout][SceneTransitionLayout]. */
fun MotionTestRule<ComposeToolkit>.recordTransition(
- fromSceneContent: @Composable SceneScope.() -> Unit,
- toSceneContent: @Composable SceneScope.() -> Unit,
+ fromSceneContent: @Composable ContentScope.() -> Unit,
+ toSceneContent: @Composable ContentScope.() -> Unit,
transition: TransitionBuilder.() -> Unit,
recordingSpec: TransitionRecordingSpec,
layoutModifier: Modifier = Modifier,
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d7c3527..ba37d58 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3681,7 +3681,10 @@
<!-- Touchpad back gesture action name in tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_back_gesture_action_title">Go back</string>
<!-- Touchpad back gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
- <string name="touchpad_back_gesture_guidance">To go back, swipe left or right using three fingers anywhere on the touchpad.</string>
+ <string name="touchpad_back_gesture_guidance">To go back, swipe left or right using three fingers anywhere on the touchpad.\n\nYou can also use the keyboard shortcut
+Action + ESC for this.</string>
+ <!-- Text shown to the user after they complete back gesture tutorial [CHAR LIMIT=NONE] -->
+ <string name="touchpad_back_gesture_finished">You completed the go back gesture.</string>
<string name="touchpad_back_gesture_animation_content_description">Touchpad showing three fingers moving right and left</string>
<string name="touchpad_back_gesture_screen_animation_content_description">Device screen showing animation for back gesture</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 7f5839d4..0da252d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2086,6 +2086,7 @@
private void handleUserUnlocked(int userId) {
Assert.isMainThread();
+ mLogger.logUserUnlocked(userId);
mUserIsUnlocked.put(userId, true);
mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
for (int i = 0; i < mCallbacks.size(); i++) {
@@ -2098,12 +2099,15 @@
private void handleUserStopped(int userId) {
Assert.isMainThread();
- mUserIsUnlocked.put(userId, mUserManager.isUserUnlocked(userId));
+ boolean isUnlocked = mUserManager.isUserUnlocked(userId);
+ mLogger.logUserStopped(userId, isUnlocked);
+ mUserIsUnlocked.put(userId, isUnlocked);
}
@VisibleForTesting
void handleUserRemoved(int userId) {
Assert.isMainThread();
+ mLogger.logUserRemoved(userId);
mUserIsUnlocked.delete(userId);
mUserTrustIsUsuallyManaged.delete(userId);
}
@@ -2444,7 +2448,9 @@
mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
int user = mSelectedUserInteractor.getSelectedUserId(true);
- mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
+ boolean isUserUnlocked = mUserManager.isUserUnlocked(user);
+ mLogger.logUserUnlockedInitialState(user, isUserUnlocked);
+ mUserIsUnlocked.put(user, isUserUnlocked);
mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
updateSecondaryLockscreenRequirement(user);
List<UserInfo> allUsers = mUserManager.getUsers();
@@ -4059,6 +4065,9 @@
pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
pw.println("ActiveUnlockRunning="
+ mTrustManager.isActiveUnlockRunning(mSelectedUserInteractor.getSelectedUserId()));
+ pw.println("userUnlockedCache[userid=" + userId + "]=" + isUserUnlocked(userId));
+ pw.println("actualUserUnlocked[userid=" + userId + "]="
+ + mUserManager.isUserUnlocked(userId));
new DumpsysTableLogger(
"KeyguardActiveUnlockTriggers",
KeyguardActiveUnlockModel.TABLE_HEADERS,
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 1f4e732..0b58f06 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -391,6 +391,7 @@
{ "handleTimeFormatUpdate timeFormat=$str1" }
)
}
+
fun logUdfpsPointerDown(sensorId: Int) {
logBuffer.log(TAG, DEBUG, { int1 = sensorId }, { "onUdfpsPointerDown, sensorId: $int1" })
}
@@ -639,12 +640,45 @@
{ "fingerprint acquire message: $int1" }
)
}
+
fun logForceIsDismissibleKeyguard(keepUnlocked: Boolean) {
logBuffer.log(
- TAG,
- DEBUG,
- { bool1 = keepUnlocked },
- { "keepUnlockedOnFold changed to: $bool1" }
+ TAG,
+ DEBUG,
+ { bool1 = keepUnlocked },
+ { "keepUnlockedOnFold changed to: $bool1" }
+ )
+ }
+
+ fun logUserUnlocked(userId: Int) {
+ logBuffer.log(TAG, DEBUG, { int1 = userId }, { "userUnlocked userId: $int1" })
+ }
+
+ fun logUserStopped(userId: Int, isUnlocked: Boolean) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = userId
+ bool1 = isUnlocked
+ },
+ { "userStopped userId: $int1 isUnlocked: $bool1" }
+ )
+ }
+
+ fun logUserRemoved(userId: Int) {
+ logBuffer.log(TAG, DEBUG, { int1 = userId }, { "userRemoved userId: $int1" })
+ }
+
+ fun logUserUnlockedInitialState(userId: Int, isUnlocked: Boolean) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = userId
+ bool1 = isUnlocked
+ },
+ { "userUnlockedInitialState userId: $int1 isUnlocked: $bool1" }
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index f041f4d..083f1db 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -52,6 +52,7 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HapClientProfile;
@@ -104,6 +105,7 @@
private final AudioManager mAudioManager;
private final LocalBluetoothProfileManager mProfileManager;
private final HapClientProfile mHapClientProfile;
+ private final UiEventLogger mUiEventLogger;
private HearingDevicesListAdapter mDeviceListAdapter;
private HearingDevicesPresetsController mPresetsController;
private Context mApplicationContext;
@@ -163,7 +165,8 @@
DialogTransitionAnimator dialogTransitionAnimator,
@Nullable LocalBluetoothManager localBluetoothManager,
@Main Handler handler,
- AudioManager audioManager) {
+ AudioManager audioManager,
+ UiEventLogger uiEventLogger) {
mApplicationContext = applicationContext;
mShowPairNewDevice = showPairNewDevice;
mSystemUIDialogFactory = systemUIDialogFactory;
@@ -174,6 +177,7 @@
mAudioManager = audioManager;
mProfileManager = localBluetoothManager.getProfileManager();
mHapClientProfile = mProfileManager.getHapClientProfile();
+ mUiEventLogger = uiEventLogger;
}
@Override
@@ -187,6 +191,7 @@
@Override
public void onDeviceItemGearClicked(@NonNull DeviceItem deviceItem, @NonNull View view) {
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK);
dismissDialogIfExists();
Intent intent = new Intent(ACTION_BLUETOOTH_DEVICE_DETAILS);
Bundle bundle = new Bundle();
@@ -198,13 +203,21 @@
}
@Override
- public void onDeviceItemOnClicked(@NonNull DeviceItem deviceItem, @NonNull View view) {
+ public void onDeviceItemClicked(@NonNull DeviceItem deviceItem, @NonNull View view) {
CachedBluetoothDevice cachedBluetoothDevice = deviceItem.getCachedBluetoothDevice();
switch (deviceItem.getType()) {
- case ACTIVE_MEDIA_BLUETOOTH_DEVICE, CONNECTED_BLUETOOTH_DEVICE ->
- cachedBluetoothDevice.disconnect();
- case AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> cachedBluetoothDevice.setActive();
- case SAVED_BLUETOOTH_DEVICE -> cachedBluetoothDevice.connect();
+ case ACTIVE_MEDIA_BLUETOOTH_DEVICE, CONNECTED_BLUETOOTH_DEVICE -> {
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT);
+ cachedBluetoothDevice.disconnect();
+ }
+ case AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_SET_ACTIVE);
+ cachedBluetoothDevice.setActive();
+ }
+ case SAVED_BLUETOOTH_DEVICE -> {
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_CONNECT);
+ cachedBluetoothDevice.connect();
+ }
}
}
@@ -262,6 +275,7 @@
if (mLocalBluetoothManager == null) {
return;
}
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW);
mPairButton = dialog.requireViewById(R.id.pair_new_device_button);
mDeviceList = dialog.requireViewById(R.id.device_list);
mPresetSpinner = dialog.requireViewById(R.id.preset_spinner);
@@ -341,12 +355,17 @@
}
});
+ // Refresh the spinner and setSelection(index, false) before setOnItemSelectedListener() to
+ // avoid extra onItemSelected() get called when first register the listener.
+ final List<BluetoothHapPresetInfo> presetInfos = mPresetsController.getAllPresetInfo();
+ final int activePresetIndex = mPresetsController.getActivePresetIndex();
+ refreshPresetInfoAdapter(presetInfos, activePresetIndex);
mPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PRESET_SELECT);
mPresetsController.selectPreset(
mPresetsController.getAllPresetInfo().get(position).getIndex());
- mPresetSpinner.setSelection(position);
}
@Override
@@ -354,9 +373,6 @@
// Do nothing
}
});
- final List<BluetoothHapPresetInfo> presetInfos = mPresetsController.getAllPresetInfo();
- final int activePresetIndex = mPresetsController.getActivePresetIndex();
- refreshPresetInfoAdapter(presetInfos, activePresetIndex);
mPresetSpinner.setVisibility(
(activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
&& !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
@@ -365,6 +381,7 @@
private void setupPairNewDeviceButton(SystemUIDialog dialog, @Visibility int visibility) {
if (visibility == VISIBLE) {
mPairButton.setOnClickListener(v -> {
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR);
dismissDialogIfExists();
final Intent intent = new Intent(Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -413,7 +430,7 @@
final int size = mPresetInfoAdapter.getCount();
for (int position = 0; position < size; position++) {
if (presetInfos.get(position).getIndex() == activePresetIndex) {
- mPresetSpinner.setSelection(position);
+ mPresetSpinner.setSelection(position, /* animate= */ false);
}
}
}
@@ -464,12 +481,15 @@
text.setText(item.getToolName());
Intent intent = item.getToolIntent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- view.setOnClickListener(
- v -> {
- dismissDialogIfExists();
- mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0,
- mDialogTransitionAnimator.createActivityTransitionController(view));
- });
+ view.setOnClickListener(v -> {
+ final String name = intent.getComponent() != null
+ ? intent.getComponent().flattenToString()
+ : intent.getPackage() + "/" + intent.getAction();
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_RELATED_TOOL_CLICK, 0, name);
+ dismissDialogIfExists();
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0,
+ mDialogTransitionAnimator.createActivityTransitionController(view));
+ });
return view;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
index 737805b..b46b8fe 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
@@ -96,7 +96,7 @@
* @param deviceItem bluetooth device item
* @param view the view that was clicked
*/
- void onDeviceItemOnClicked(@NonNull DeviceItem deviceItem, @NonNull View view);
+ void onDeviceItemClicked(@NonNull DeviceItem deviceItem, @NonNull View view);
}
private static class DeviceItemViewHolder extends RecyclerView.ViewHolder {
@@ -119,7 +119,7 @@
public void bindView(DeviceItem item, HearingDeviceItemCallback callback) {
mContainer.setEnabled(item.isEnabled());
- mContainer.setOnClickListener(view -> callback.onDeviceItemOnClicked(item, view));
+ mContainer.setOnClickListener(view -> callback.onDeviceItemClicked(item, view));
Integer backgroundResId = item.getBackground();
if (backgroundResId != null) {
mContainer.setBackground(mContext.getDrawable(item.getBackground()));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java
new file mode 100644
index 0000000..3fbe56e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+public enum HearingDevicesUiEvent implements UiEventLogger.UiEventEnum {
+
+ @UiEvent(doc = "Hearing devices dialog is shown")
+ HEARING_DEVICES_DIALOG_SHOW(1848),
+ @UiEvent(doc = "Pair new device")
+ HEARING_DEVICES_PAIR(1849),
+ @UiEvent(doc = "Connect to the device")
+ HEARING_DEVICES_CONNECT(1850),
+ @UiEvent(doc = "Disconnect from the device")
+ HEARING_DEVICES_DISCONNECT(1851),
+ @UiEvent(doc = "Set the device as active device")
+ HEARING_DEVICES_SET_ACTIVE(1852),
+ @UiEvent(doc = "Click on the device gear to enter device detail page")
+ HEARING_DEVICES_GEAR_CLICK(1853),
+ @UiEvent(doc = "Select a preset from preset spinner")
+ HEARING_DEVICES_PRESET_SELECT(1854),
+ @UiEvent(doc = "Click on related tool")
+ HEARING_DEVICES_RELATED_TOOL_CLICK(1856);
+
+ private final int mId;
+
+ HearingDevicesUiEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 88601da..4286646 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -30,6 +30,7 @@
import com.android.systemui.dreams.DreamMonitor
import com.android.systemui.dreams.homecontrols.HomeControlsDreamStartable
import com.android.systemui.globalactions.GlobalActionsComponent
+import com.android.systemui.inputdevice.oobe.KeyboardTouchpadOobeTutorialCoreStartable
import com.android.systemui.keyboard.KeyboardUI
import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable
import com.android.systemui.keyguard.KeyguardViewConfigurator
@@ -257,6 +258,13 @@
@Binds
@IntoMap
+ @ClassKey(KeyboardTouchpadOobeTutorialCoreStartable::class)
+ abstract fun bindOobeSchedulerCoreStartable(
+ listener: KeyboardTouchpadOobeTutorialCoreStartable
+ ): CoreStartable
+
+ @Binds
+ @IntoMap
@ClassKey(PhysicalKeyboardCoreStartable::class)
abstract fun bindKeyboardCoreStartable(listener: PhysicalKeyboardCoreStartable): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 15ddf5b..a448072 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -144,6 +144,7 @@
import com.android.systemui.statusbar.window.StatusBarWindowModule;
import com.android.systemui.telephony.data.repository.TelephonyRepositoryModule;
import com.android.systemui.temporarydisplay.dagger.TemporaryDisplayModule;
+import com.android.systemui.touchpad.TouchpadModule;
import com.android.systemui.tuner.dagger.TunerModule;
import com.android.systemui.user.UserModule;
import com.android.systemui.user.domain.UserDomainLayerModule;
@@ -259,6 +260,7 @@
CommonSystemUIUnfoldModule.class,
TelephonyRepositoryModule.class,
TemporaryDisplayModule.class,
+ TouchpadModule.class,
TunerModule.class,
UserDomainLayerModule.class,
UserModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt
index 3b161b6..5a008bd 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt
@@ -45,7 +45,7 @@
data class DeviceAdded(val deviceId: Int) : DeviceChange
- data object DeviceRemoved : DeviceChange
+ data class DeviceRemoved(val deviceId: Int) : DeviceChange
data object FreshStart : DeviceChange
@@ -72,7 +72,7 @@
override fun onInputDeviceRemoved(deviceId: Int) {
connectedDevices = connectedDevices - deviceId
- sendWithLogging(connectedDevices to DeviceRemoved)
+ sendWithLogging(connectedDevices to DeviceRemoved(deviceId))
}
}
sendWithLogging(connectedDevices to FreshStart)
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt
new file mode 100644
index 0000000..dbfea76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputdevice.oobe
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.inputdevice.oobe.domain.interactor.OobeTutorialSchedulerInteractor
+import com.android.systemui.shared.Flags.newTouchpadGesturesTutorial
+import dagger.Lazy
+import javax.inject.Inject
+
+/** A [CoreStartable] to launch a scheduler for keyboard and touchpad OOBE education */
+@SysUISingleton
+class KeyboardTouchpadOobeTutorialCoreStartable
+@Inject
+constructor(private val oobeTutorialSchedulerInteractor: Lazy<OobeTutorialSchedulerInteractor>) :
+ CoreStartable {
+ override fun start() {
+ if (newTouchpadGesturesTutorial()) {
+ oobeTutorialSchedulerInteractor.get().start()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeTutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeTutorialSchedulerInteractor.kt
new file mode 100644
index 0000000..0d69081
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeTutorialSchedulerInteractor.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.inputdevice.oobe.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import com.android.systemui.touchpad.data.repository.TouchpadRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** When keyboards or touchpads are connected, schedule a tutorial after given time has elapsed */
+@SysUISingleton
+class OobeTutorialSchedulerInteractor
+@Inject
+constructor(
+ @Application private val context: Context,
+ @Application private val applicationScope: CoroutineScope,
+ keyboardRepository: KeyboardRepository,
+ touchpadRepository: TouchpadRepository
+) {
+ private val isAnyKeyboardConnected = keyboardRepository.isAnyKeyboardConnected
+ private val isAnyTouchpadConnected = touchpadRepository.isAnyTouchpadConnected
+
+ fun start() {
+ applicationScope.launch { isAnyKeyboardConnected.collect { startOobe() } }
+ applicationScope.launch { isAnyTouchpadConnected.collect { startOobe() } }
+ }
+
+ private fun startOobe() {
+ val intent = Intent(TUTORIAL_ACTION)
+ intent.addCategory(Intent.CATEGORY_DEFAULT)
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(intent)
+ }
+
+ companion object {
+ const val TAG = "OobeSchedulerInteractor"
+ const val TUTORIAL_ACTION = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
index 817849c..b654307 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
@@ -41,6 +41,7 @@
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
@@ -78,9 +79,15 @@
) : KeyboardRepository {
private val keyboardsChange: Flow<Pair<Collection<Int>, DeviceChange>> =
- inputDeviceRepository.deviceChange.map { (ids, change) ->
- ids.filter { id -> isPhysicalFullKeyboard(id) } to change
- }
+ inputDeviceRepository.deviceChange
+ .map { (ids, change) -> ids.filter { id -> isPhysicalFullKeyboard(id) } to change }
+ .filter { (_, change) ->
+ when (change) {
+ FreshStart -> true
+ is DeviceAdded -> isPhysicalFullKeyboard(change.deviceId)
+ is DeviceRemoved -> isPhysicalFullKeyboard(change.deviceId)
+ }
+ }
@FlowPreview
override val newlyConnectedKeyboard: Flow<Keyboard> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index e2d7851..04ea37e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -112,14 +112,18 @@
keyguardInteractor.isActiveDreamLockscreenHosted,
communalSceneInteractor.isIdleOnCommunal
)
- .filterRelevantKeyguardState()
- .collect {
- (isBouncerShowing, isAwake, isActiveDreamLockscreenHosted, isIdleOnCommunal)
- ->
+ .filterRelevantKeyguardStateAnd { (isBouncerShowing, _, _, _) ->
+ // TODO(b/307976454) - See if we need to listen for SHOW_WHEN_LOCKED
+ // activities showing up over the bouncer. Camera launch can't show up over
+ // bouncer since the first power press hides bouncer. Do occluding
+ // activities auto hide bouncer? Not sure.
+ !isBouncerShowing
+ }
+ .collect { (_, isAwake, isActiveDreamLockscreenHosted, isIdleOnCommunal) ->
if (
!maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
startTransitionTo(state, ownerReason = reason)
- } && !isBouncerShowing && isAwake && !isActiveDreamLockscreenHosted
+ } && isAwake && !isActiveDreamLockscreenHosted
) {
val toState =
if (isIdleOnCommunal) {
@@ -254,6 +258,6 @@
val TO_GONE_SHORT_DURATION = 200.milliseconds
val TO_LOCKSCREEN_DURATION = 450.milliseconds
val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
- val TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD = 0.5f
+ val TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD = 0.1f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
index 805dbb0..2ebd9e8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
@@ -30,6 +30,7 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -52,11 +53,12 @@
* then we'll seed the repository with a transition from OFF -> GONE.
*/
@OptIn(ExperimentalCoroutinesApi::class)
- private val showLockscreenOnBoot =
+ private val showLockscreenOnBoot: Flow<Boolean> by lazy {
deviceProvisioningInteractor.isDeviceProvisioned.map { provisioned ->
(provisioned || deviceEntryInteractor.isAuthenticationRequired()) &&
deviceEntryInteractor.isLockscreenEnabled()
}
+ }
override fun start() {
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 4bfefda..3fffeff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.res.Resources
-import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.ContentKey
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
@@ -90,12 +90,12 @@
/**
* Returns a flow that indicates whether lockscreen notifications should be rendered in the
- * given [sceneKey].
+ * given [contentKey].
*/
- fun areNotificationsVisible(sceneKey: SceneKey): Flow<Boolean> {
+ fun areNotificationsVisible(contentKey: ContentKey): Flow<Boolean> {
// `Scenes.NotificationsShade` renders its own separate notifications stack, so when it's
// open we avoid rendering the lockscreen notifications stack.
- if (sceneKey == Scenes.NotificationsShade) {
+ if (contentKey == Scenes.NotificationsShade) {
return flowOf(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 2a8db56..a6ca3ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -98,7 +98,8 @@
globalSettings.registerContentObserverSync(
globalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
/* notifyForDescendants = */ true,
- observer)
+ observer
+ )
// QQQ: Do we need to register for SETTING_HEADS_UP_TICKER? It seems unused.
@@ -147,12 +148,12 @@
}
}
-class PeekDndSuppressor() :
+class PeekDndSuppressor :
VisualInterruptionFilter(types = setOf(PEEK), reason = "suppressed by DND") {
override fun shouldSuppress(entry: NotificationEntry) = entry.shouldSuppressPeek()
}
-class PeekNotImportantSuppressor() :
+class PeekNotImportantSuppressor :
VisualInterruptionFilter(types = setOf(PEEK), reason = "importance < HIGH") {
override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_HIGH
}
@@ -170,7 +171,10 @@
class PeekOldWhenSuppressor(private val systemClock: SystemClock) :
VisualInterruptionFilter(
- types = setOf(PEEK), reason = "has old `when`", uiEventId = HUN_SUPPRESSED_OLD_WHEN) {
+ types = setOf(PEEK),
+ reason = "has old `when`",
+ uiEventId = HUN_SUPPRESSED_OLD_WHEN
+ ) {
private fun whenAge(entry: NotificationEntry) =
systemClock.currentTimeMillis() - entry.sbn.notification.getWhen()
@@ -190,47 +194,51 @@
}
}
-class PulseEffectSuppressor() :
+class PulseEffectSuppressor :
VisualInterruptionFilter(types = setOf(PULSE), reason = "suppressed by DND") {
override fun shouldSuppress(entry: NotificationEntry) = entry.shouldSuppressAmbient()
}
-class PulseLockscreenVisibilityPrivateSuppressor() :
+class PulseLockscreenVisibilityPrivateSuppressor :
VisualInterruptionFilter(
- types = setOf(PULSE), reason = "hidden by lockscreen visibility override") {
+ types = setOf(PULSE),
+ reason = "hidden by lockscreen visibility override"
+ ) {
override fun shouldSuppress(entry: NotificationEntry) =
entry.ranking.lockscreenVisibilityOverride == VISIBILITY_PRIVATE
}
-class PulseLowImportanceSuppressor() :
+class PulseLowImportanceSuppressor :
VisualInterruptionFilter(types = setOf(PULSE), reason = "importance < DEFAULT") {
override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_DEFAULT
}
-class HunGroupAlertBehaviorSuppressor() :
+class HunGroupAlertBehaviorSuppressor :
VisualInterruptionFilter(
- types = setOf(PEEK, PULSE), reason = "suppressive group alert behavior") {
+ types = setOf(PEEK, PULSE),
+ reason = "suppressive group alert behavior"
+ ) {
override fun shouldSuppress(entry: NotificationEntry) =
entry.sbn.let { it.isGroup && it.notification.suppressAlertingDueToGrouping() }
}
-class HunSilentNotificationSuppressor() :
+class HunSilentNotificationSuppressor :
VisualInterruptionFilter(types = setOf(PEEK, PULSE), reason = "notification isSilent") {
override fun shouldSuppress(entry: NotificationEntry) =
entry.sbn.let { Flags.notificationSilentFlag() && it.notification.isSilent }
}
-class HunJustLaunchedFsiSuppressor() :
+class HunJustLaunchedFsiSuppressor :
VisualInterruptionFilter(types = setOf(PEEK, PULSE), reason = "just launched FSI") {
override fun shouldSuppress(entry: NotificationEntry) = entry.hasJustLaunchedFullScreenIntent()
}
-class BubbleNotAllowedSuppressor() :
- VisualInterruptionFilter(types = setOf(BUBBLE), reason = "cannot bubble") {
+class BubbleNotAllowedSuppressor :
+ VisualInterruptionFilter(types = setOf(BUBBLE), reason = "cannot bubble", isSpammy = true) {
override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble()
}
-class BubbleNoMetadataSuppressor() :
+class BubbleNoMetadataSuppressor :
VisualInterruptionFilter(types = setOf(BUBBLE), reason = "has no or invalid bubble metadata") {
private fun isValidMetadata(metadata: BubbleMetadata?) =
@@ -253,6 +261,7 @@
/**
* Set with:
+ *
* adb shell setprop persist.force_show_avalanche_edu_once 1 && adb shell stop; adb shell start
*/
private const val FORCE_SHOW_AVALANCHE_EDU_ONCE = "persist.force_show_avalanche_edu_once"
@@ -368,7 +377,8 @@
val bundle = Bundle()
bundle.putString(
Notification.EXTRA_SUBSTITUTE_APP_NAME,
- context.getString(com.android.internal.R.string.android_system_label))
+ context.getString(com.android.internal.R.string.android_system_label)
+ )
val builder =
Notification.Builder(context, NotificationChannels.ALERTS)
@@ -390,8 +400,10 @@
}
private fun calculateState(entry: NotificationEntry): State {
- if (entry.ranking.isConversation &&
- entry.sbn.notification.getWhen() > avalancheProvider.startTime) {
+ if (
+ entry.ranking.isConversation &&
+ entry.sbn.notification.getWhen() > avalancheProvider.startTime
+ ) {
uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_NEW_CONVERSATION)
return State.ALLOW_CONVERSATION_AFTER_AVALANCHE
}
@@ -424,8 +436,10 @@
uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_COLORIZED)
return State.ALLOW_COLORIZED
}
- if (packageManager.checkPermission(RECEIVE_EMERGENCY_BROADCAST, entry.sbn.packageName) ==
- PERMISSION_GRANTED) {
+ if (
+ packageManager.checkPermission(RECEIVE_EMERGENCY_BROADCAST, entry.sbn.packageName) ==
+ PERMISSION_GRANTED
+ ) {
uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_EMERGENCY)
return State.ALLOW_EMERGENCY
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
index 1470b03..c204ea9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.interruption
+import android.util.Log
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.log.core.LogLevel.INFO
@@ -24,11 +25,15 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision
import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.util.Compile
import javax.inject.Inject
class VisualInterruptionDecisionLogger
@Inject
constructor(@NotificationInterruptLog val buffer: LogBuffer) {
+
+ val spew: Boolean = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
+
fun logHeadsUpFeatureChanged(isEnabled: Boolean) {
buffer.log(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index c0d27cb..8e8d9b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -95,7 +95,8 @@
private constructor(
val decision: DecisionImpl,
override val uiEventId: UiEventEnum? = null,
- override val eventLogData: EventLogData? = null
+ override val eventLogData: EventLogData? = null,
+ val isSpammy: Boolean = false,
) : Loggable {
companion object {
val unsuppressed =
@@ -113,7 +114,8 @@
LoggableDecision(
DecisionImpl(shouldInterrupt = false, logReason = suppressor.reason),
uiEventId = suppressor.uiEventId,
- eventLogData = suppressor.eventLogData
+ eventLogData = suppressor.eventLogData,
+ isSpammy = suppressor.isSpammy,
)
}
}
@@ -185,8 +187,15 @@
if (NotificationAvalancheSuppression.isEnabled) {
addFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor,
- packageManager, uiEventLogger, context, notificationManager)
+ AvalancheSuppressor(
+ avalancheProvider,
+ systemClock,
+ settingsInteractor,
+ packageManager,
+ uiEventLogger,
+ context,
+ notificationManager
+ )
)
avalancheProvider.register()
}
@@ -280,7 +289,9 @@
entry: NotificationEntry,
loggableDecision: LoggableDecision
) {
- logger.logDecision(type.name, entry, loggableDecision.decision)
+ if (!loggableDecision.isSpammy || logger.spew) {
+ logger.logDecision(type.name, entry, loggableDecision.decision)
+ }
logEvents(entry, loggableDecision)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
index ee79727..5fe75c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
@@ -59,6 +59,10 @@
/** Optional data to be logged in the EventLog when this suppresses an interruption. */
val eventLogData: EventLogData?
+ /** Whether the interruption is spammy and should be dropped under normal circumstances. */
+ val isSpammy: Boolean
+ get() = false
+
/**
* Called after the suppressor is added to the [VisualInterruptionDecisionProvider] but before
* any other methods are called on the suppressor.
@@ -76,7 +80,7 @@
constructor(
types: Set<VisualInterruptionType>,
reason: String
- ) : this(types, reason, /* uiEventId = */ null)
+ ) : this(types, reason, /* uiEventId= */ null)
/** @return true if these interruptions should be suppressed right now. */
abstract fun shouldSuppress(): Boolean
@@ -87,12 +91,13 @@
override val types: Set<VisualInterruptionType>,
override val reason: String,
override val uiEventId: UiEventEnum? = null,
- override val eventLogData: EventLogData? = null
+ override val eventLogData: EventLogData? = null,
+ override val isSpammy: Boolean = false,
) : VisualInterruptionSuppressor {
constructor(
types: Set<VisualInterruptionType>,
reason: String
- ) : this(types, reason, /* uiEventId = */ null)
+ ) : this(types, reason, /* uiEventId= */ null)
/**
* @param entry the notification to consider suppressing
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 461a38d..b6de78e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2995,7 +2995,7 @@
@Override
public void onFalse() {
// Hides quick settings, bouncer, and quick-quick settings.
- mStatusBarKeyguardViewManager.reset(true);
+ mStatusBarKeyguardViewManager.reset(true, /* isFalsingReset= */true);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 41b69a7..88a2b23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -708,7 +708,7 @@
* Shows the notification keyguard or the bouncer depending on
* {@link #needsFullscreenBouncer()}.
*/
- protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
+ protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
boolean isDozing = mDozing;
if (Flags.simPinRaceConditionOnRestart()) {
KeyguardState toState = mKeyguardTransitionInteractor.getTransitionState().getValue()
@@ -734,8 +734,12 @@
mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
}
}
- } else {
- Log.e(TAG, "Attempted to show the sim bouncer when it is already showing.");
+ } else if (!isFalsingReset) {
+ // Falsing resets can cause this to flicker, so don't reset in this case
+ Log.i(TAG, "Sim bouncer is already showing, issuing a refresh");
+ mPrimaryBouncerInteractor.hide();
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+
}
} else {
mCentralSurfaces.showKeyguard();
@@ -957,6 +961,10 @@
@Override
public void reset(boolean hideBouncerWhenShowing) {
+ reset(hideBouncerWhenShowing, /* isFalsingReset= */false);
+ }
+
+ public void reset(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
if (mKeyguardStateController.isShowing() && !bouncerIsAnimatingAway()) {
final boolean isOccluded = mKeyguardStateController.isOccluded();
// Hide quick settings.
@@ -968,7 +976,7 @@
hideBouncer(false /* destroyView */);
}
} else {
- showBouncerOrKeyguard(hideBouncerWhenShowing);
+ showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset);
}
if (hideBouncerWhenShowing) {
hideAlternateBouncer(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index a7c5f78..03ec41d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -20,6 +20,7 @@
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import android.telephony.satellite.NtnSignalStrengthCallback
+import android.telephony.satellite.SatelliteCommunicationAllowedStateCallback
import android.telephony.satellite.SatelliteManager
import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS
import android.telephony.satellite.SatelliteModemStateCallback
@@ -37,7 +38,6 @@
import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog
import com.android.systemui.statusbar.pipeline.dagger.VerboseDeviceBasedSatelliteInputLog
import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository
-import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.POLLING_INTERVAL_MS
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Companion.whenSupported
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.NotSupported
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Supported
@@ -60,11 +60,9 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
@@ -122,15 +120,9 @@
}
/**
- * Basically your everyday run-of-the-mill system service listener, with three notable exceptions.
+ * Basically your everyday run-of-the-mill system service listener, with two notable exceptions.
*
- * First, there is an availability bit that we are tracking via [SatelliteManager]. See
- * [isSatelliteAllowedForCurrentLocation] for the implementation details. The thing to note about
- * this bit is that there is no callback that exists. Therefore we implement a simple polling
- * mechanism here. Since the underlying bit is location-dependent, we simply poll every hour (see
- * [POLLING_INTERVAL_MS]) and see what the current state is.
- *
- * Secondly, there are cases when simply requesting information from SatelliteManager can fail. See
+ * First, there are cases when simply requesting information from SatelliteManager can fail. See
* [SatelliteSupport] for details on how we track the state. What's worth noting here is that
* SUPPORTED is a stronger guarantee than [satelliteManager] being null. Therefore, the fundamental
* data flows here ([connectionState], [signalStrength],...) are wrapped in the convenience method
@@ -138,7 +130,7 @@
* [SupportedSatelliteManager], we can guarantee that the manager is non-null AND that it has told
* us that satellite is supported. Therefore, we don't expect exceptions to be thrown.
*
- * Lastly, this class is designed to wait a full minute of process uptime before making any requests
+ * Second, this class is designed to wait a full minute of process uptime before making any requests
* to the satellite manager. The hope is that by waiting we don't have to retry due to a modem that
* is still booting up or anything like that. We can tune or remove this behavior in the future if
* necessary.
@@ -158,8 +150,6 @@
private val satelliteManager: SatelliteManager?
- override val isSatelliteAllowedForCurrentLocation: MutableStateFlow<Boolean>
-
// Some calls into satellite manager will throw exceptions if it is not supported.
// This is never expected to change after boot, but may need to be retried in some cases
@get:VisibleForTesting
@@ -221,8 +211,6 @@
init {
satelliteManager = satelliteManagerOpt.getOrNull()
- isSatelliteAllowedForCurrentLocation = MutableStateFlow(false)
-
if (satelliteManager != null) {
// Outer scope launch allows us to delay until MIN_UPTIME
scope.launch {
@@ -233,10 +221,7 @@
{ "Checked for system support. support=$str1" },
)
- // Second, launch a job to poll for service availability based on location
- scope.launch { pollForAvailabilityBasedOnLocation() }
-
- // Third, register a listener to let us know if there are changes to support
+ // Second, register a listener to let us know if there are changes to support
scope.launch { listenForChangesToSatelliteSupport(satelliteManager) }
}
} else {
@@ -259,28 +244,43 @@
return sm.checkSatelliteSupported()
}
- /*
- * As there is no listener available for checking satellite allowed, we must poll the service.
- * Defaulting to polling at most once every 20m while active. Subsequent OOS events will restart
- * the job, so a flaky connection might cause more frequent checks.
- */
- private suspend fun pollForAvailabilityBasedOnLocation() {
+ override val isSatelliteAllowedForCurrentLocation =
satelliteSupport
.whenSupported(
- supported = ::isSatelliteAllowedHasListener,
+ supported = ::isSatelliteAvailableFlow,
orElse = flowOf(false),
retrySignal = telephonyProcessCrashedEvent,
)
- .collectLatest { hasSubscribers ->
- if (hasSubscribers) {
- while (true) {
- logBuffer.i { "requestIsCommunicationAllowedForCurrentLocation" }
- checkIsSatelliteAllowed()
- delay(POLLING_INTERVAL_MS)
+ .stateIn(scope, SharingStarted.Lazily, false)
+
+ private fun isSatelliteAvailableFlow(sm: SupportedSatelliteManager): Flow<Boolean> =
+ conflatedCallbackFlow {
+ val callback = SatelliteCommunicationAllowedStateCallback { allowed ->
+ logBuffer.i({ bool1 = allowed }) {
+ "onSatelliteCommunicationAllowedStateChanged: $bool1"
+ }
+
+ trySend(allowed)
+ }
+
+ var registered = false
+ try {
+ sm.registerForCommunicationAllowedStateChanged(
+ bgDispatcher.asExecutor(),
+ callback
+ )
+ registered = true
+ } catch (e: Exception) {
+ logBuffer.e("Error calling registerForCommunicationAllowedStateChanged", e)
+ }
+
+ awaitClose {
+ if (registered) {
+ sm.unregisterForCommunicationAllowedStateChanged(callback)
}
}
}
- }
+ .flowOn(bgDispatcher)
/**
* Register a callback with [SatelliteManager] to let us know if there is a change in satellite
@@ -410,14 +410,6 @@
}
}
- /**
- * Signal that we should start polling [checkIsSatelliteAllowed]. We only need to poll if there
- * are active listeners to [isSatelliteAllowedForCurrentLocation]
- */
- @SuppressWarnings("unused")
- private fun isSatelliteAllowedHasListener(sm: SupportedSatelliteManager): Flow<Boolean> =
- isSatelliteAllowedForCurrentLocation.subscriptionCount.map { it > 0 }.distinctUntilChanged()
-
override val connectionState =
satelliteSupport
.whenSupported(
@@ -485,28 +477,6 @@
}
.flowOn(bgDispatcher)
- /** Fire off a request to check for satellite availability. Always runs on the bg context */
- private suspend fun checkIsSatelliteAllowed() =
- withContext(bgDispatcher) {
- satelliteManager?.requestIsCommunicationAllowedForCurrentLocation(
- bgDispatcher.asExecutor(),
- object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
- override fun onError(e: SatelliteManager.SatelliteException) {
- logBuffer.e(
- "Found exception when checking availability",
- e,
- )
- isSatelliteAllowedForCurrentLocation.value = false
- }
-
- override fun onResult(allowed: Boolean) {
- logBuffer.i { "isSatelliteAllowedForCurrentLocation: $allowed" }
- isSatelliteAllowedForCurrentLocation.value = allowed
- }
- }
- )
- }
-
private suspend fun SatelliteManager.checkSatelliteSupported(): SatelliteSupport =
suspendCancellableCoroutine { continuation ->
val cb =
@@ -546,9 +516,6 @@
}
companion object {
- // TTL for satellite polling is twenty minutes
- const val POLLING_INTERVAL_MS: Long = 1000 * 60 * 20
-
// Let the system boot up and stabilize before we check for system support
const val MIN_UPTIME: Long = 1000 * 60
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/TouchpadModule.kt b/packages/SystemUI/src/com/android/systemui/touchpad/TouchpadModule.kt
new file mode 100644
index 0000000..c86ac2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/TouchpadModule.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.touchpad
+
+import com.android.systemui.touchpad.data.repository.TouchpadRepository
+import com.android.systemui.touchpad.data.repository.TouchpadRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+abstract class TouchpadModule {
+
+ @Binds
+ abstract fun bindTouchpadRepository(repository: TouchpadRepositoryImpl): TouchpadRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/data/repository/TouchpadRepository.kt b/packages/SystemUI/src/com/android/systemui/touchpad/data/repository/TouchpadRepository.kt
new file mode 100644
index 0000000..7131546
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/data/repository/TouchpadRepository.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.touchpad.data.repository
+
+import android.hardware.input.InputManager
+import android.view.InputDevice.SOURCE_TOUCHPAD
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.inputdevice.data.repository.InputDeviceRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+interface TouchpadRepository {
+ /** Emits true if any touchpad is connected to the device, false otherwise. */
+ val isAnyTouchpadConnected: Flow<Boolean>
+}
+
+@SysUISingleton
+class TouchpadRepositoryImpl
+@Inject
+constructor(
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val inputManager: InputManager,
+ inputDeviceRepository: InputDeviceRepository
+) : TouchpadRepository {
+
+ override val isAnyTouchpadConnected: Flow<Boolean> =
+ inputDeviceRepository.deviceChange
+ .map { (ids, _) -> ids.any { id -> isTouchpad(id) } }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+
+ private fun isTouchpad(deviceId: Int): Boolean {
+ val device = inputManager.getInputDevice(deviceId) ?: return false
+ return device.supportsSource(SOURCE_TOUCHPAD)
+ }
+
+ companion object {
+ const val TAG = "TouchpadRepositoryImpl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index 4e1829a..51dfef0 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -182,7 +182,9 @@
if (gestureState == FINISHED) R.string.touchpad_tutorial_gesture_done
else R.string.touchpad_back_gesture_action_title,
titleColor = screenColors.titleColor,
- bodyTextId = R.string.touchpad_back_gesture_guidance,
+ bodyTextId =
+ if (gestureState == FINISHED) R.string.touchpad_back_gesture_finished
+ else R.string.touchpad_back_gesture_guidance,
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(76.dp))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index 5ea5c21..d3b7d22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -52,6 +52,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
@@ -123,6 +124,8 @@
@Mock
private AudioManager mAudioManager;
@Mock
+ private UiEventLogger mUiEventLogger;
+ @Mock
private CachedBluetoothDevice mCachedDevice;
@Mock
private BluetoothDevice mDevice;
@@ -179,6 +182,7 @@
anyInt(), any());
assertThat(intentCaptor.getValue().getAction()).isEqualTo(
Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS);
+ verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR);
}
@Test
@@ -192,7 +196,7 @@
anyInt(), any());
assertThat(intentCaptor.getValue().getAction()).isEqualTo(
HearingDevicesDialogDelegate.ACTION_BLUETOOTH_DEVICE_DETAILS);
-
+ verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK);
}
@Test
@@ -200,9 +204,10 @@
setUpDeviceListDialog();
when(mHearingDeviceItem.getType()).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE);
- mDialogDelegate.onDeviceItemOnClicked(mHearingDeviceItem, new View(mContext));
+ mDialogDelegate.onDeviceItemClicked(mHearingDeviceItem, new View(mContext));
verify(mCachedDevice).disconnect();
+ verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT);
}
@Test
@@ -304,7 +309,8 @@
mDialogTransitionAnimator,
mLocalBluetoothManager,
new Handler(mTestableLooper.getLooper()),
- mAudioManager
+ mAudioManager,
+ mUiEventLogger
);
mDialog = mDialogDelegate.createDialog();
@@ -326,7 +332,8 @@
mDialogTransitionAnimator,
mLocalBluetoothManager,
new Handler(mTestableLooper.getLooper()),
- mAudioManager
+ mAudioManager,
+ mUiEventLogger
);
mDialog = mDialogDelegate.createDialog();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
index e01744e..6a43a61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
@@ -20,6 +20,7 @@
import android.content.ContextWrapper
import android.content.SharedPreferences
import android.os.Handler
+import android.platform.test.annotations.DisableFlags
import android.provider.Settings
import android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
import android.provider.Settings.Global.ZEN_MODE_OFF
@@ -61,6 +62,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@DisableFlags(android.app.Flags.FLAG_MODES_UI)
class DndTileTest : SysuiTestCase() {
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index af5e60e..9b61105 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -1068,7 +1068,7 @@
public void testShowBouncerOrKeyguard_needsFullScreen() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.SimPin);
- mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
verify(mCentralSurfaces).hideKeyguard();
verify(mPrimaryBouncerInteractor).show(true);
}
@@ -1084,7 +1084,7 @@
.thenReturn(KeyguardState.LOCKSCREEN);
reset(mCentralSurfaces);
- mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
verify(mPrimaryBouncerInteractor).show(true);
verify(mCentralSurfaces).showKeyguard();
}
@@ -1092,11 +1092,26 @@
@Test
@DisableSceneContainer
public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() {
+ boolean isFalsingReset = false;
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.SimPin);
when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
- mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset);
verify(mCentralSurfaces, never()).hideKeyguard();
+ verify(mPrimaryBouncerInteractor).show(true);
+ }
+
+ @Test
+ @DisableSceneContainer
+ public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing_onFalsing() {
+ boolean isFalsingReset = true;
+ when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+ KeyguardSecurityModel.SecurityMode.SimPin);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset);
+ verify(mCentralSurfaces, never()).hideKeyguard();
+
+ // Do not refresh the full screen bouncer if the call is from falsing
verify(mPrimaryBouncerInteractor, never()).show(true);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 73ac6e3..af4f647 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -22,6 +22,7 @@
import android.telephony.TelephonyManager
import android.telephony.satellite.NtnSignalStrength
import android.telephony.satellite.NtnSignalStrengthCallback
+import android.telephony.satellite.SatelliteCommunicationAllowedStateCallback
import android.telephony.satellite.SatelliteManager
import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED
import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING
@@ -44,7 +45,6 @@
import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers
import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.MIN_UPTIME
-import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.POLLING_INTERVAL_MS
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
@@ -54,11 +54,8 @@
import java.util.Optional
import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -71,6 +68,7 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doThrow
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@@ -186,152 +184,86 @@
}
@Test
- fun isSatelliteAllowed_readsSatelliteManagerState_enabled() =
+ fun isSatelliteAllowed_listensToSatelliteManagerCallback() =
testScope.runTest {
setupDefaultRepo()
- // GIVEN satellite is allowed in this location
- val allowed = true
-
- doAnswer {
- val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
- receiver.onResult(allowed)
- null
- }
- .`when`(satelliteManager)
- .requestIsCommunicationAllowedForCurrentLocation(
- any(),
- any<OutcomeReceiver<Boolean, SatelliteException>>()
- )
val latest by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
+ runCurrent()
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isSatelliteAllowed_readsSatelliteManagerState_disabled() =
- testScope.runTest {
- setupDefaultRepo()
- // GIVEN satellite is not allowed in this location
- val allowed = false
-
- doAnswer {
- val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
- receiver.onResult(allowed)
- null
+ val callback =
+ withArgCaptor<SatelliteCommunicationAllowedStateCallback> {
+ verify(satelliteManager)
+ .registerForCommunicationAllowedStateChanged(any(), capture())
}
- .`when`(satelliteManager)
- .requestIsCommunicationAllowedForCurrentLocation(
- any(),
- any<OutcomeReceiver<Boolean, SatelliteException>>()
- )
- val latest by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
+ // WHEN satellite manager says it's not available
+ callback.onSatelliteCommunicationAllowedStateChanged(false)
- assertThat(latest).isFalse()
- }
-
- @Test
- fun isSatelliteAllowed_pollsOnTimeout() =
- testScope.runTest {
- setupDefaultRepo()
- // GIVEN satellite is not allowed in this location
- var allowed = false
-
- doAnswer {
- val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
- receiver.onResult(allowed)
- null
- }
- .`when`(satelliteManager)
- .requestIsCommunicationAllowedForCurrentLocation(
- any(),
- any<OutcomeReceiver<Boolean, SatelliteException>>()
- )
-
- val latest by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
-
+ // THEN it's not!
assertThat(latest).isFalse()
- // WHEN satellite becomes enabled
- allowed = true
+ // WHEN satellite manager says it's changed to available
+ callback.onSatelliteCommunicationAllowedStateChanged(true)
- // WHEN the timeout has not yet been reached
- advanceTimeBy(POLLING_INTERVAL_MS / 2)
-
- // THEN the value is still false
- assertThat(latest).isFalse()
-
- // WHEN time advances beyond the polling interval
- advanceTimeBy(POLLING_INTERVAL_MS / 2 + 1)
-
- // THEN then new value is emitted
+ // THEN it is!
assertThat(latest).isTrue()
}
@Test
- fun isSatelliteAllowed_pollingRestartsWhenCollectionRestarts() =
- testScope.runTest {
- setupDefaultRepo()
- // Use the old school launch/cancel so we can simulate subscribers arriving and leaving
-
- var latest: Boolean? = false
- var job =
- underTest.isSatelliteAllowedForCurrentLocation.onEach { latest = it }.launchIn(this)
-
- // GIVEN satellite is not allowed in this location
- var allowed = false
-
- doAnswer {
- val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
- receiver.onResult(allowed)
- null
- }
- .`when`(satelliteManager)
- .requestIsCommunicationAllowedForCurrentLocation(
- any(),
- any<OutcomeReceiver<Boolean, SatelliteException>>()
- )
-
- assertThat(latest).isFalse()
-
- // WHEN satellite becomes enabled
- allowed = true
-
- // WHEN the job is restarted
- advanceTimeBy(POLLING_INTERVAL_MS / 2)
-
- job.cancel()
- job =
- underTest.isSatelliteAllowedForCurrentLocation.onEach { latest = it }.launchIn(this)
-
- // THEN the value is re-fetched
- assertThat(latest).isTrue()
-
- job.cancel()
- }
-
- @Test
fun isSatelliteAllowed_falseWhenErrorOccurs() =
testScope.runTest {
setupDefaultRepo()
- doAnswer {
- val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
- receiver.onError(SatelliteException(1 /* unused */))
- null
- }
- .`when`(satelliteManager)
- .requestIsCommunicationAllowedForCurrentLocation(
- any(),
- any<OutcomeReceiver<Boolean, SatelliteException>>()
- )
+ // GIVEN SatelliteManager gon' throw exceptions when we ask to register the callback
+ doThrow(RuntimeException("Test exception"))
+ .`when`(satelliteManager)
+ .registerForCommunicationAllowedStateChanged(any(), any())
+
+ // WHEN the latest value is requested (and thus causes an exception to be thrown)
val latest by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
+ // THEN the value is just false, and we didn't crash!
assertThat(latest).isFalse()
}
@Test
+ fun isSatelliteAllowed_reRegistersOnTelephonyProcessCrash() =
+ testScope.runTest {
+ setupDefaultRepo()
+ val latest by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
+ runCurrent()
+
+ val callback =
+ withArgCaptor<SatelliteCommunicationAllowedStateCallback> {
+ verify(satelliteManager)
+ .registerForCommunicationAllowedStateChanged(any(), capture())
+ }
+
+ val telephonyCallback =
+ MobileTelephonyHelpers.getTelephonyCallbackForType<
+ TelephonyCallback.RadioPowerStateListener
+ >(
+ telephonyManager
+ )
+
+ // GIVEN satellite is currently provisioned
+ callback.onSatelliteCommunicationAllowedStateChanged(true)
+
+ assertThat(latest).isTrue()
+
+ // WHEN a crash event happens (detected by radio state change)
+ telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_ON)
+ runCurrent()
+ telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_OFF)
+ runCurrent()
+
+ // THEN listener is re-registered
+ verify(satelliteManager, times(2))
+ .registerForCommunicationAllowedStateChanged(any(), any())
+ }
+
+ @Test
fun satelliteProvisioned_notSupported_defaultFalse() =
testScope.runTest {
// GIVEN satellite is not supported
@@ -363,24 +295,21 @@
testScope.runTest {
// GIVEN satellite is supported on device
doAnswer {
- val callback: OutcomeReceiver<Boolean, SatelliteException> =
- it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException>
- callback.onResult(true)
- }
+ val callback: OutcomeReceiver<Boolean, SatelliteException> =
+ it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException>
+ callback.onResult(true)
+ }
.whenever(satelliteManager)
.requestIsSupported(any(), any())
// GIVEN satellite returns an error when asked if provisioned
doAnswer {
- val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
- receiver.onError(SatelliteException(SATELLITE_RESULT_ERROR))
- null
- }
+ val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
+ receiver.onError(SatelliteException(SATELLITE_RESULT_ERROR))
+ null
+ }
.whenever(satelliteManager)
- .requestIsProvisioned(
- any(),
- any<OutcomeReceiver<Boolean, SatelliteException>>()
- )
+ .requestIsProvisioned(any(), any<OutcomeReceiver<Boolean, SatelliteException>>())
// GIVEN we've been up long enough to start querying
systemClock.setUptimeMillis(Process.getStartUptimeMillis() + MIN_UPTIME)
@@ -409,10 +338,10 @@
testScope.runTest {
// GIVEN satellite is supported on device
doAnswer {
- val callback: OutcomeReceiver<Boolean, SatelliteException> =
- it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException>
- callback.onResult(true)
- }
+ val callback: OutcomeReceiver<Boolean, SatelliteException> =
+ it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException>
+ callback.onResult(true)
+ }
.whenever(satelliteManager)
.requestIsSupported(any(), any())
@@ -779,10 +708,10 @@
.requestIsSupported(any(), any())
doAnswer {
- val callback: OutcomeReceiver<Boolean, SatelliteException> =
- it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException>
- callback.onResult(initialSatelliteIsProvisioned)
- }
+ val callback: OutcomeReceiver<Boolean, SatelliteException> =
+ it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException>
+ callback.onResult(initialSatelliteIsProvisioned)
+ }
.whenever(satelliteManager)
.requestIsProvisioned(any(), any())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/data/repository/TouchpadRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/data/repository/TouchpadRepositoryTest.kt
new file mode 100644
index 0000000..3783af5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/data/repository/TouchpadRepositoryTest.kt
@@ -0,0 +1,184 @@
+/*
+ * 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.touchpad.data.repository
+
+import android.hardware.input.FakeInputManager
+import android.hardware.input.InputManager.InputDeviceListener
+import android.hardware.input.fakeInputManager
+import android.testing.TestableLooper
+import android.view.InputDevice
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.inputdevice.data.repository.InputDeviceRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidJUnit4::class)
+class TouchpadRepositoryTest : SysuiTestCase() {
+
+ @Captor private lateinit var deviceListenerCaptor: ArgumentCaptor<InputDeviceListener>
+ private lateinit var fakeInputManager: FakeInputManager
+
+ private lateinit var underTest: TouchpadRepository
+ private lateinit var dispatcher: CoroutineDispatcher
+ private lateinit var inputDeviceRepo: InputDeviceRepository
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ fakeInputManager = testKosmos().fakeInputManager
+ dispatcher = StandardTestDispatcher()
+ testScope = TestScope(dispatcher)
+ val handler = FakeHandler(TestableLooper.get(this).looper)
+ inputDeviceRepo =
+ InputDeviceRepository(handler, testScope.backgroundScope, fakeInputManager.inputManager)
+ underTest =
+ TouchpadRepositoryImpl(dispatcher, fakeInputManager.inputManager, inputDeviceRepo)
+ }
+
+ @Test
+ fun emitsDisconnected_ifNothingIsConnected() =
+ testScope.runTest {
+ val initialState = underTest.isAnyTouchpadConnected.first()
+ assertThat(initialState).isFalse()
+ }
+
+ @Test
+ fun emitsConnected_ifTouchpadAlreadyConnectedAtTheStart() =
+ testScope.runTest {
+ fakeInputManager.addDevice(TOUCHPAD_ID, TOUCHPAD)
+ val initialValue = underTest.isAnyTouchpadConnected.first()
+ assertThat(initialValue).isTrue()
+ }
+
+ @Test
+ fun emitsConnected_whenNewTouchpadConnects() =
+ testScope.runTest {
+ captureDeviceListener()
+ val isTouchpadConnected by collectLastValue(underTest.isAnyTouchpadConnected)
+
+ fakeInputManager.addDevice(TOUCHPAD_ID, TOUCHPAD)
+
+ assertThat(isTouchpadConnected).isTrue()
+ }
+
+ @Test
+ fun emitsDisconnected_whenDeviceWithIdDoesNotExist() =
+ testScope.runTest {
+ captureDeviceListener()
+ val isTouchpadConnected by collectLastValue(underTest.isAnyTouchpadConnected)
+ whenever(fakeInputManager.inputManager.getInputDevice(eq(NULL_DEVICE_ID)))
+ .thenReturn(null)
+ fakeInputManager.addDevice(NULL_DEVICE_ID, InputDevice.SOURCE_UNKNOWN)
+ assertThat(isTouchpadConnected).isFalse()
+ }
+
+ @Test
+ fun emitsDisconnected_whenTouchpadDisconnects() =
+ testScope.runTest {
+ captureDeviceListener()
+ val isTouchpadConnected by collectLastValue(underTest.isAnyTouchpadConnected)
+
+ fakeInputManager.addDevice(TOUCHPAD_ID, TOUCHPAD)
+ assertThat(isTouchpadConnected).isTrue()
+
+ fakeInputManager.removeDevice(TOUCHPAD_ID)
+ assertThat(isTouchpadConnected).isFalse()
+ }
+
+ private suspend fun captureDeviceListener() {
+ underTest.isAnyTouchpadConnected.first()
+ Mockito.verify(fakeInputManager.inputManager)
+ .registerInputDeviceListener(deviceListenerCaptor.capture(), anyOrNull())
+ fakeInputManager.registerInputDeviceListener(deviceListenerCaptor.value)
+ }
+
+ @Test
+ fun emitsDisconnected_whenNonTouchpadConnects() =
+ testScope.runTest {
+ captureDeviceListener()
+ val isTouchpadConnected by collectLastValue(underTest.isAnyTouchpadConnected)
+
+ fakeInputManager.addDevice(NON_TOUCHPAD_ID, InputDevice.SOURCE_KEYBOARD)
+ assertThat(isTouchpadConnected).isFalse()
+ }
+
+ @Test
+ fun emitsDisconnected_whenTouchpadDisconnectsAndWasAlreadyConnectedAtTheStart() =
+ testScope.runTest {
+ captureDeviceListener()
+ val isTouchpadConnected by collectLastValue(underTest.isAnyTouchpadConnected)
+
+ fakeInputManager.removeDevice(TOUCHPAD_ID)
+ assertThat(isTouchpadConnected).isFalse()
+ }
+
+ @Test
+ fun emitsConnected_whenAnotherDeviceDisconnects() =
+ testScope.runTest {
+ captureDeviceListener()
+ val isTouchpadConnected by collectLastValue(underTest.isAnyTouchpadConnected)
+
+ fakeInputManager.addDevice(TOUCHPAD_ID, TOUCHPAD)
+ fakeInputManager.removeDevice(NON_TOUCHPAD_ID)
+
+ assertThat(isTouchpadConnected).isTrue()
+ }
+
+ @Test
+ fun emitsConnected_whenOneTouchpadDisconnectsButAnotherRemainsConnected() =
+ testScope.runTest {
+ captureDeviceListener()
+ val isTouchpadConnected by collectLastValue(underTest.isAnyTouchpadConnected)
+
+ fakeInputManager.addDevice(TOUCHPAD_ID, TOUCHPAD)
+ fakeInputManager.addDevice(ANOTHER_TOUCHPAD_ID, TOUCHPAD)
+ fakeInputManager.removeDevice(TOUCHPAD_ID)
+
+ assertThat(isTouchpadConnected).isTrue()
+ }
+
+ private companion object {
+ private const val TOUCHPAD_ID = 1
+ private const val NON_TOUCHPAD_ID = 2
+ private const val ANOTHER_TOUCHPAD_ID = 3
+ private const val NULL_DEVICE_ID = 4
+
+ private const val TOUCHPAD = InputDevice.SOURCE_TOUCHPAD
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
index 6e7c05c..ee36cad 100644
--- a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
+++ b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
@@ -16,6 +16,7 @@
package android.hardware.input
+import android.hardware.input.InputManager.InputDeviceListener
import android.view.InputDevice
import android.view.KeyCharacterMap
import android.view.KeyCharacterMap.VIRTUAL_KEYBOARD
@@ -47,6 +48,8 @@
VIRTUAL_KEYBOARD to allKeyCodes.toMutableSet()
)
+ private var inputDeviceListener: InputDeviceListener? = null
+
val inputManager =
mock<InputManager> {
whenever(getInputDevice(anyInt())).thenAnswer { invocation ->
@@ -84,6 +87,11 @@
addPhysicalKeyboard(deviceId, enabled)
}
+ fun registerInputDeviceListener(listener: InputDeviceListener) {
+ // TODO (b/355422259): handle this by listening to inputManager.registerInputDeviceListener
+ inputDeviceListener = listener
+ }
+
fun addPhysicalKeyboard(id: Int, enabled: Boolean = true) {
check(id > 0) { "Physical keyboard ids have to be > 0" }
addKeyboard(id, enabled)
@@ -106,6 +114,16 @@
supportedKeyCodesByDeviceId[id] = allKeyCodes.toMutableSet()
}
+ fun addDevice(id: Int, sources: Int) {
+ devices[id] = InputDevice.Builder().setId(id).setSources(sources).build()
+ inputDeviceListener?.onInputDeviceAdded(id)
+ }
+
+ fun removeDevice(id: Int) {
+ devices.remove(id)
+ inputDeviceListener?.onInputDeviceRemoved(id)
+ }
+
private fun InputDevice.copy(
id: Int = getId(),
type: Int = keyboardType,
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
index 8fe6853..1a15d7a 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
@@ -25,19 +25,24 @@
import static android.os.ParcelFileDescriptor.MODE_WORLD_WRITEABLE;
import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
import com.android.internal.annotations.GuardedBy;
import com.android.ravenwood.common.JvmWorkaround;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Map;
public class ParcelFileDescriptor_host {
+ private static final String TAG = "ParcelFileDescriptor_host";
+
/**
* Since we don't have a great way to keep an unmanaged {@code FileDescriptor} reference
* alive, we keep a strong reference to the {@code RandomAccessFile} we used to open it. This
@@ -98,16 +103,18 @@
synchronized (sActive) {
raf = sActive.remove(fd);
}
+ int fdInt = JvmWorkaround.getInstance().getFdInt(fd);
try {
if (raf != null) {
raf.close();
} else {
- // Odd, we don't remember opening this ourselves, but let's release the
- // underlying resource as requested
- System.err.println("Closing unknown FileDescriptor: " + fd);
- new FileOutputStream(fd).close();
+ // This FD wasn't created by native_open$ravenwood().
+ // The FD was passed to the PFD ctor. Just close it.
+ Os.close(fd);
}
- } catch (IOException ignored) {
+ } catch (IOException | ErrnoException e) {
+ Log.w(TAG, "Exception thrown while closing fd " + fdInt, e);
}
}
}
+;
\ No newline at end of file
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java
index 22e11e1..2df93cd 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java
@@ -15,6 +15,11 @@
*/
package com.android.platform.test.ravenwood.nativesubstitution;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import java.io.FileDescriptor;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@@ -31,6 +36,8 @@
* {@link ByteBuffer} wouldn't allow...)
*/
public class Parcel_host {
+ private static final String TAG = "Parcel";
+
private Parcel_host() {
}
@@ -50,6 +57,11 @@
// TODO Use the actual value from Parcel.java.
private static final int OK = 0;
+ private final Map<Integer, FileDescriptor> mFdMap = new ConcurrentHashMap<>();
+
+ private static final int FD_PLACEHOLDER = 0xDEADBEEF;
+ private static final int FD_PAYLOAD_SIZE = 8;
+
private void validate() {
if (mDeleted) {
// TODO: Put more info
@@ -67,6 +79,7 @@
return p;
}
+ /** Native method substitution */
public static long nativeCreate() {
final long id = sNextId.getAndIncrement();
final Parcel_host p = new Parcel_host();
@@ -80,7 +93,8 @@
mSize = 0;
mPos = 0;
mSensitive = false;
- mAllowFds = false;
+ mAllowFds = true;
+ mFdMap.clear();
}
private void updateSize() {
@@ -89,16 +103,19 @@
}
}
+ /** Native method substitution */
public static void nativeDestroy(long nativePtr) {
getInstance(nativePtr).mDeleted = true;
sInstances.remove(nativePtr);
}
+ /** Native method substitution */
public static void nativeFreeBuffer(long nativePtr) {
getInstance(nativePtr).freeBuffer();
}
- public void freeBuffer() {
+ /** Native method substitution */
+ private void freeBuffer() {
init();
}
@@ -137,32 +154,47 @@
}
}
+ /** Native method substitution */
public static void nativeMarkSensitive(long nativePtr) {
getInstance(nativePtr).mSensitive = true;
}
+
+ /** Native method substitution */
public static int nativeDataSize(long nativePtr) {
return getInstance(nativePtr).mSize;
}
+
+ /** Native method substitution */
public static int nativeDataAvail(long nativePtr) {
var p = getInstance(nativePtr);
return p.mSize - p.mPos;
}
+
+ /** Native method substitution */
public static int nativeDataPosition(long nativePtr) {
return getInstance(nativePtr).mPos;
}
+
+ /** Native method substitution */
public static int nativeDataCapacity(long nativePtr) {
return getInstance(nativePtr).mBuffer.length;
}
+
+ /** Native method substitution */
public static void nativeSetDataSize(long nativePtr, int size) {
var p = getInstance(nativePtr);
p.ensureCapacity(size);
getInstance(nativePtr).mSize = size;
}
+
+ /** Native method substitution */
public static void nativeSetDataPosition(long nativePtr, int pos) {
var p = getInstance(nativePtr);
// TODO: Should this change the size or the capacity??
p.mPos = pos;
}
+
+ /** Native method substitution */
public static void nativeSetDataCapacity(long nativePtr, int size) {
if (size < 0) {
throw new IllegalArgumentException("size < 0: size=" + size);
@@ -173,20 +205,25 @@
}
}
+ /** Native method substitution */
public static boolean nativePushAllowFds(long nativePtr, boolean allowFds) {
var p = getInstance(nativePtr);
var prev = p.mAllowFds;
p.mAllowFds = allowFds;
return prev;
}
+
+ /** Native method substitution */
public static void nativeRestoreAllowFds(long nativePtr, boolean lastValue) {
getInstance(nativePtr).mAllowFds = lastValue;
}
+ /** Native method substitution */
public static void nativeWriteByteArray(long nativePtr, byte[] b, int offset, int len) {
nativeWriteBlob(nativePtr, b, offset, len);
}
+ /** Native method substitution */
public static void nativeWriteBlob(long nativePtr, byte[] b, int offset, int len) {
var p = getInstance(nativePtr);
@@ -205,6 +242,7 @@
}
}
+ /** Native method substitution */
public static int nativeWriteInt(long nativePtr, int value) {
var p = getInstance(nativePtr);
p.ensureMoreCapacity(Integer.BYTES);
@@ -219,14 +257,19 @@
return OK;
}
+ /** Native method substitution */
public static int nativeWriteLong(long nativePtr, long value) {
nativeWriteInt(nativePtr, (int) (value >>> 32));
nativeWriteInt(nativePtr, (int) (value));
return OK;
}
+
+ /** Native method substitution */
public static int nativeWriteFloat(long nativePtr, float val) {
return nativeWriteInt(nativePtr, Float.floatToIntBits(val));
}
+
+ /** Native method substitution */
public static int nativeWriteDouble(long nativePtr, double val) {
return nativeWriteLong(nativePtr, Double.doubleToLongBits(val));
}
@@ -235,6 +278,7 @@
return ((val + 3) / 4) * 4;
}
+ /** Native method substitution */
public static void nativeWriteString8(long nativePtr, String val) {
if (val == null) {
nativeWriteBlob(nativePtr, null, 0, 0);
@@ -243,15 +287,19 @@
nativeWriteBlob(nativePtr, bytes, 0, bytes.length);
}
}
+
+ /** Native method substitution */
public static void nativeWriteString16(long nativePtr, String val) {
// Just reuse String8
nativeWriteString8(nativePtr, val);
}
+ /** Native method substitution */
public static byte[] nativeCreateByteArray(long nativePtr) {
return nativeReadBlob(nativePtr);
}
+ /** Native method substitution */
public static boolean nativeReadByteArray(long nativePtr, byte[] dest, int destLen) {
if (dest == null) {
return false;
@@ -271,6 +319,7 @@
return true;
}
+ /** Native method substitution */
public static byte[] nativeReadBlob(long nativePtr) {
var p = getInstance(nativePtr);
if (p.mSize - p.mPos < 4) {
@@ -295,6 +344,8 @@
return bytes;
}
+
+ /** Native method substitution */
public static int nativeReadInt(long nativePtr) {
var p = getInstance(nativePtr);
@@ -310,19 +361,24 @@
return ret;
}
+
+ /** Native method substitution */
public static long nativeReadLong(long nativePtr) {
return (((long) nativeReadInt(nativePtr)) << 32)
| (((long) nativeReadInt(nativePtr)) & 0xffff_ffffL);
}
+ /** Native method substitution */
public static float nativeReadFloat(long nativePtr) {
return Float.intBitsToFloat(nativeReadInt(nativePtr));
}
+ /** Native method substitution */
public static double nativeReadDouble(long nativePtr) {
return Double.longBitsToDouble(nativeReadLong(nativePtr));
}
+ /** Native method substitution */
public static String nativeReadString8(long nativePtr) {
final var bytes = nativeReadBlob(nativePtr);
if (bytes == null) {
@@ -334,10 +390,13 @@
return nativeReadString8(nativePtr);
}
+ /** Native method substitution */
public static byte[] nativeMarshall(long nativePtr) {
var p = getInstance(nativePtr);
return Arrays.copyOf(p.mBuffer, p.mSize);
}
+
+ /** Native method substitution */
public static void nativeUnmarshall(
long nativePtr, byte[] data, int offset, int length) {
var p = getInstance(nativePtr);
@@ -346,6 +405,8 @@
p.mPos += length;
p.updateSize();
}
+
+ /** Native method substitution */
public static int nativeCompareData(long thisNativePtr, long otherNativePtr) {
var a = getInstance(thisNativePtr);
var b = getInstance(otherNativePtr);
@@ -355,6 +416,8 @@
return -1;
}
}
+
+ /** Native method substitution */
public static boolean nativeCompareDataInRange(
long ptrA, int offsetA, long ptrB, int offsetB, int length) {
var a = getInstance(ptrA);
@@ -368,6 +431,8 @@
return Arrays.equals(Arrays.copyOfRange(a.mBuffer, offsetA, offsetA + length),
Arrays.copyOfRange(b.mBuffer, offsetB, offsetB + length));
}
+
+ /** Native method substitution */
public static void nativeAppendFrom(
long thisNativePtr, long otherNativePtr, int srcOffset, int length) {
var dst = getInstance(thisNativePtr);
@@ -382,25 +447,83 @@
// TODO: Update the other's position?
}
- public static boolean nativeHasFileDescriptors(long nativePtr) {
- // Assume false for now, because we don't support writing FDs yet.
- return false;
- }
-
- public static boolean nativeHasFileDescriptorsInRange(
- long nativePtr, int offset, int length) {
- // Assume false for now, because we don't support writing FDs yet.
- return false;
- }
-
+ /** Native method substitution */
public static boolean nativeHasBinders(long nativePtr) {
// Assume false for now, because we don't support adding binders.
return false;
}
+ /** Native method substitution */
public static boolean nativeHasBindersInRange(
long nativePtr, int offset, int length) {
// Assume false for now, because we don't support writing FDs yet.
return false;
}
-}
+
+ /** Native method substitution */
+ public static void nativeWriteFileDescriptor(long nativePtr, java.io.FileDescriptor val) {
+ var p = getInstance(nativePtr);
+
+ if (!p.mAllowFds) {
+ // Simulate the FDS_NOT_ALLOWED case in frameworks/base/core/jni/android_util_Binder.cpp
+ throw new RuntimeException("Not allowed to write file descriptors here");
+ }
+
+ FileDescriptor dup = null;
+ try {
+ dup = Os.dup(val);
+ } catch (ErrnoException e) {
+ throw new RuntimeException(e);
+ }
+ p.mFdMap.put(p.mPos, dup);
+
+ // Parcel.cpp writes two int32s for a FD.
+ // Make sure FD_PAYLOAD_SIZE is in sync with this code.
+ nativeWriteInt(nativePtr, FD_PLACEHOLDER);
+ nativeWriteInt(nativePtr, FD_PLACEHOLDER);
+ }
+
+ /** Native method substitution */
+ public static java.io.FileDescriptor nativeReadFileDescriptor(long nativePtr) {
+ var p = getInstance(nativePtr);
+
+ var pos = p.mPos;
+ var fd = p.mFdMap.get(pos);
+
+ if (fd == null) {
+ Log.w(TAG, "nativeReadFileDescriptor: Not a FD at pos #" + pos);
+ return null;
+ }
+ nativeReadInt(nativePtr);
+ return fd;
+ }
+
+ /** Native method substitution */
+ public static boolean nativeHasFileDescriptors(long nativePtr) {
+ var p = getInstance(nativePtr);
+ return p.mFdMap.size() > 0;
+ }
+
+ /** Native method substitution */
+ public static boolean nativeHasFileDescriptorsInRange(long nativePtr, int offset, int length) {
+ var p = getInstance(nativePtr);
+
+ // Original code: hasFileDescriptorsInRange() in frameworks/native/libs/binder/Parcel.cpp
+ if (offset < 0 || length < 0) {
+ throw new IllegalArgumentException("Negative value not allowed: offset=" + offset
+ + " length=" + length);
+ }
+ long limit = (long) offset + (long) length;
+ if (limit > p.mSize) {
+ throw new IllegalArgumentException("Out of range: offset=" + offset
+ + " length=" + length + " dataSize=" + p.mSize);
+ }
+
+ for (var pos : p.mFdMap.keySet()) {
+ if (offset <= pos && (pos + FD_PAYLOAD_SIZE - 1) < (offset + length)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index 8a1fe62..825ab72 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -53,4 +53,9 @@
public static StructStat stat(String path) throws ErrnoException {
return RavenwoodRuntimeNative.stat(path);
}
+
+ /** Ravenwood version of the OS API. */
+ public static void close(FileDescriptor fd) throws ErrnoException {
+ RavenwoodRuntimeNative.close(fd);
+ }
}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
index e9b305e..2bc8e71 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
@@ -48,6 +48,8 @@
public static native StructStat stat(String path) throws ErrnoException;
+ private static native void nClose(int fd) throws ErrnoException;
+
public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
}
@@ -83,4 +85,11 @@
return nFstat(fdInt);
}
+
+ /** See close(2) */
+ public static void close(FileDescriptor fd) throws ErrnoException {
+ var fdInt = JvmWorkaround.getInstance().getFdInt(fd);
+
+ nClose(fdInt);
+ }
}
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index e0a3e1c..ee84954 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -167,6 +167,11 @@
return doStat(env, javaPath, false);
}
+static void nClose(JNIEnv* env, jclass, jint fd) {
+ // Don't use TEMP_FAILURE_RETRY() on close(): https://lkml.org/lkml/2005/9/10/129
+ throwIfMinusOne(env, "close", close(fd));
+}
+
// ---- Registration ----
static const JNINativeMethod sMethods[] =
@@ -179,6 +184,7 @@
{ "nFstat", "(I)Landroid/system/StructStat;", (void*)nFstat },
{ "lstat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_lstat },
{ "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
+ { "nClose", "(I)V", (void*)nClose },
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 30c743e..9067cda 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -41,6 +41,7 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
import static android.view.accessibility.AccessibilityManager.FlashNotificationReason;
+import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -57,7 +58,6 @@
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
-import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.accessibilityservice.AccessibilityGestureEvent;
@@ -825,25 +825,27 @@
@VisibleForTesting
boolean onPackagesForceStoppedLocked(
String[] packages, AccessibilityUserState userState) {
- final List<String> continuousServicePackages =
+ final Set<String> packageSet = new HashSet<>(List.of(packages));
+ final ArrayList<ComponentName> continuousServices = new ArrayList<>(
userState.mInstalledServices.stream().filter(service ->
(service.flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON)
== FLAG_REQUEST_ACCESSIBILITY_BUTTON
- ).map(service -> service.getComponentName().flattenToString()).toList();
+ ).map(AccessibilityServiceInfo::getComponentName).toList());
+
+ // Filter out continuous packages that are not from the array of stopped packages.
+ continuousServices.removeIf(
+ continuousName -> !packageSet.contains(continuousName.getPackageName()));
boolean enabledServicesChanged = false;
final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
while (it.hasNext()) {
final ComponentName comp = it.next();
final String compPkg = comp.getPackageName();
- for (String pkg : packages) {
- if (compPkg.equals(pkg)) {
- it.remove();
- userState.getBindingServicesLocked().remove(comp);
- userState.getCrashedServicesLocked().remove(comp);
- enabledServicesChanged = true;
- break;
- }
+ if (packageSet.contains(compPkg)) {
+ it.remove();
+ userState.getBindingServicesLocked().remove(comp);
+ userState.getCrashedServicesLocked().remove(comp);
+ enabledServicesChanged = true;
}
}
if (enabledServicesChanged) {
@@ -855,8 +857,8 @@
// Remove any button targets that match any stopped continuous services
Set<String> buttonTargets = userState.getShortcutTargetsLocked(SOFTWARE);
boolean buttonTargetsChanged = buttonTargets.removeIf(
- target -> continuousServicePackages.stream().anyMatch(
- pkg -> Objects.equals(target, pkg)));
+ target -> continuousServices.stream().anyMatch(
+ continuousName -> continuousName.flattenToString().equals(target)));
if (buttonTargetsChanged) {
userState.updateShortcutTargetsLocked(buttonTargets, SOFTWARE);
persistColonDelimitedSetToSettingLocked(
@@ -2641,7 +2643,8 @@
}
}
- private <T> void persistColonDelimitedSetToSettingLocked(String settingName, int userId,
+ @VisibleForTesting
+ <T> void persistColonDelimitedSetToSettingLocked(String settingName, int userId,
Set<T> set, Function<T, String> toString) {
persistColonDelimitedSetToSettingLocked(settingName, userId, set,
toString, /* defaultEmptyString= */ null);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index e9c3fbd..0ee5896 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -523,6 +523,7 @@
mScaleGestureDetector = new ScaleGestureDetector(context, this, Handler.getMain());
mScaleGestureDetector.setQuickScaleEnabled(false);
mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain());
+ mScrollGestureDetector.setIsLongpressEnabled(false);
}
@Override
@@ -1658,11 +1659,12 @@
}
float dX = event.getX() - firstPointerDownLocation.x;
float dY = event.getY() - firstPointerDownLocation.y;
- if (isAtLeftEdge() && dX > 0) {
+ if (isAtLeftEdge() && isScrollingLeft(dX, dY)) {
return OVERSCROLL_LEFT_EDGE;
- } else if (isAtRightEdge() && dX < 0) {
+ } else if (isAtRightEdge() && isScrollingRight(dX, dY)) {
return OVERSCROLL_RIGHT_EDGE;
- } else if ((isAtTopEdge() && dY > 0) || (isAtBottomEdge() && dY < 0)) {
+ } else if ((isAtTopEdge() && isScrollingUp(dX, dY))
+ || (isAtBottomEdge() && isScrollingDown(dX, dY))) {
return OVERSCROLL_VERTICAL_EDGE;
}
return OVERSCROLL_NONE;
@@ -1672,18 +1674,34 @@
return mFullScreenMagnificationController.isAtLeftEdge(mDisplayId, mOverscrollEdgeSlop);
}
+ private static boolean isScrollingLeft(float dX, float dY) {
+ return Math.abs(dX) > Math.abs(dY) && dX > 0;
+ }
+
private boolean isAtRightEdge() {
return mFullScreenMagnificationController.isAtRightEdge(mDisplayId, mOverscrollEdgeSlop);
}
+ private static boolean isScrollingRight(float dX, float dY) {
+ return Math.abs(dX) > Math.abs(dY) && dX < 0;
+ }
+
private boolean isAtTopEdge() {
return mFullScreenMagnificationController.isAtTopEdge(mDisplayId, mOverscrollEdgeSlop);
}
+ private static boolean isScrollingUp(float dX, float dY) {
+ return Math.abs(dX) < Math.abs(dY) && dY > 0;
+ }
+
private boolean isAtBottomEdge() {
return mFullScreenMagnificationController.isAtBottomEdge(mDisplayId, mOverscrollEdgeSlop);
}
+ private static boolean isScrollingDown(float dX, float dY) {
+ return Math.abs(dX) < Math.abs(dY) && dY < 0;
+ }
+
private boolean pointerValid(PointF pointerDownLocation) {
return !(Float.isNaN(pointerDownLocation.x) && Float.isNaN(pointerDownLocation.y));
}
@@ -1876,6 +1894,7 @@
private MotionEventInfo mEvent;
SinglePanningState(Context context) {
mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain());
+ mScrollGestureDetector.setIsLongpressEnabled(false);
}
@Override
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index f336120..3b0147c 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -149,21 +149,6 @@
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
}
- if (opts != null && opts.isPendingIntentBackgroundActivityLaunchAllowedByPermission()) {
- Slog.wtf(TAG,
- "Resetting option pendingIntentBackgroundActivityLaunchAllowedByPermission"
- + " which is set by the pending intent creator ("
- + packageName
- + ") because this option is meant for the pending intent sender");
- if (CompatChanges.isChangeEnabled(PendingIntent.PENDING_INTENT_OPTIONS_CHECK,
- callingUid)) {
- throw new IllegalArgumentException(
- "pendingIntentBackgroundActivityLaunchAllowedByPermission "
- + "can not be set by creator of a PendingIntent");
- }
- opts.setPendingIntentBackgroundActivityLaunchAllowedByPermission(false);
- }
-
final boolean noCreate = (flags & PendingIntent.FLAG_NO_CREATE) != 0;
final boolean cancelCurrent = (flags & PendingIntent.FLAG_CANCEL_CURRENT) != 0;
final boolean updateCurrent = (flags & PendingIntent.FLAG_UPDATE_CURRENT) != 0;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index ca69f31..8d8a54e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -31,6 +31,7 @@
import static android.media.audio.Flags.automaticBtDeviceType;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.media.audio.Flags.asDeviceConnectionFailure;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -529,6 +530,17 @@
}
};
+ /**
+ * package-protected for unit testing only
+ * Returns the currently connected devices
+ * @return the collection of connected devices
+ */
+ /*package*/ @NonNull Collection<DeviceInfo> getConnectedDevices() {
+ synchronized (mDevicesLock) {
+ return mConnectedDevices.values();
+ }
+ }
+
// List of devices actually connected to AudioPolicy (through AudioSystem), only one
// by device type, which is used as the key, value is the DeviceInfo generated key.
// For the moment only for A2DP sink devices.
@@ -598,8 +610,9 @@
/**
* Class to store info about connected devices.
* Use makeDeviceListKey() to make a unique key for this list.
+ * Package-protected for unit tests
*/
- private static class DeviceInfo {
+ /*package*/ static class DeviceInfo {
final int mDeviceType;
final @NonNull String mDeviceName;
final @NonNull String mDeviceAddress;
@@ -762,13 +775,27 @@
// Always executed on AudioDeviceBroker message queue
/*package*/ void onRestoreDevices() {
synchronized (mDevicesLock) {
+ int res;
+ List<DeviceInfo> failedReconnectionDeviceList = new ArrayList<>(/*initialCapacity*/ 0);
//TODO iterate on mApmConnectedDevices instead once it handles all device types
for (DeviceInfo di : mConnectedDevices.values()) {
- mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(di.mDeviceType,
+ res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
+ di.mDeviceType,
di.mDeviceAddress,
di.mDeviceName),
AudioSystem.DEVICE_STATE_AVAILABLE,
di.mDeviceCodecFormat);
+ if (asDeviceConnectionFailure() && res != AudioSystem.AUDIO_STATUS_OK) {
+ failedReconnectionDeviceList.add(di);
+ }
+ }
+ if (asDeviceConnectionFailure()) {
+ for (DeviceInfo di : failedReconnectionDeviceList) {
+ AudioService.sDeviceLogger.enqueueAndSlog(
+ "Device inventory restore failed to reconnect " + di,
+ EventLogger.Event.ALOGE, TAG);
+ mConnectedDevices.remove(di.getKey(), di);
+ }
}
mAppliedStrategyRolesInt.clear();
mAppliedPresetRolesInt.clear();
@@ -2070,8 +2097,9 @@
"APM failed to make available A2DP device addr="
+ Utils.anonymizeBluetoothAddress(address)
+ " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
- // TODO: connection failed, stop here
- // TODO: return;
+ if (asDeviceConnectionFailure()) {
+ return;
+ }
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"A2DP sink device addr=" + Utils.anonymizeBluetoothAddress(address)
@@ -2336,8 +2364,7 @@
"APM failed to make unavailable A2DP device addr="
+ Utils.anonymizeBluetoothAddress(address)
+ " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
- // TODO: failed to disconnect, stop here
- // TODO: return;
+ // not taking further action: proceeding as if disconnection from APM worked
} else {
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"A2DP device addr=" + Utils.anonymizeBluetoothAddress(address)
@@ -2383,8 +2410,9 @@
"APM failed to make available A2DP source device addr="
+ Utils.anonymizeBluetoothAddress(address)
+ " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
- // TODO: connection failed, stop here
- // TODO: return
+ if (asDeviceConnectionFailure()) {
+ return;
+ }
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"A2DP source device addr=" + Utils.anonymizeBluetoothAddress(address)
@@ -2402,6 +2430,7 @@
mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_UNAVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
+ // always remove regardless of the result
mConnectedDevices.remove(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
@@ -2418,9 +2447,18 @@
AudioDeviceAttributes ada = new AudioDeviceAttributes(
DEVICE_OUT_HEARING_AID, address, name);
- mAudioSystem.setDeviceConnectionState(ada,
+ final int res = mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_AVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
+ if (asDeviceConnectionFailure() && res != AudioSystem.AUDIO_STATUS_OK) {
+ AudioService.sDeviceLogger.enqueueAndSlog(
+ "APM failed to make available HearingAid addr=" + address
+ + " error=" + res,
+ EventLogger.Event.ALOGE, TAG);
+ return;
+ }
+ AudioService.sDeviceLogger.enqueueAndSlog("HearingAid made available addr=" + address,
+ EventLogger.Event.ALOGI, TAG);
mConnectedDevices.put(
DeviceInfo.makeDeviceListKey(DEVICE_OUT_HEARING_AID, address),
new DeviceInfo(DEVICE_OUT_HEARING_AID, name, address));
@@ -2447,6 +2485,7 @@
mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_UNAVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
+ // always remove regardless of return code
mConnectedDevices.remove(
DeviceInfo.makeDeviceListKey(DEVICE_OUT_HEARING_AID, address));
// Remove Hearing Aid routes as well
@@ -2540,11 +2579,12 @@
final int res = mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_AVAILABLE, codec);
if (res != AudioSystem.AUDIO_STATUS_OK) {
- AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueueAndSlog(
"APM failed to make available LE Audio device addr=" + address
- + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
- // TODO: connection failed, stop here
- // TODO: return;
+ + " error=" + res, EventLogger.Event.ALOGE, TAG);
+ if (asDeviceConnectionFailure()) {
+ return;
+ }
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"LE Audio " + (AudioSystem.isInputDevice(device) ? "source" : "sink")
@@ -2596,8 +2636,7 @@
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM failed to make unavailable LE Audio device addr=" + address
+ " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
- // TODO: failed to disconnect, stop here
- // TODO: return;
+ // not taking further action: proceeding as if disconnection from APM worked
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"LE Audio device addr=" + Utils.anonymizeBluetoothAddress(address)
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 1183768..ac43e86 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -63,6 +63,7 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.media.audio.Flags.absVolumeIndexFix;
import static com.android.media.audio.Flags.alarmMinVolumeZero;
+import static com.android.media.audio.Flags.asDeviceConnectionFailure;
import static com.android.media.audio.Flags.audioserverPermissions;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
import static com.android.media.audio.Flags.replaceStreamBtSco;
@@ -4306,7 +4307,8 @@
super.getVolumeGroupVolumeIndex_enforcePermission();
synchronized (VolumeStreamState.class) {
if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
- throw new IllegalArgumentException("No volume group for id " + groupId);
+ Log.e(TAG, "No volume group for id " + groupId);
+ return 0;
}
VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
// Return 0 when muted, not min index since for e.g. Voice Call, it has a non zero
@@ -4322,7 +4324,8 @@
super.getVolumeGroupMaxVolumeIndex_enforcePermission();
synchronized (VolumeStreamState.class) {
if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
- throw new IllegalArgumentException("No volume group for id " + groupId);
+ Log.e(TAG, "No volume group for id " + groupId);
+ return 0;
}
VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
return vgs.getMaxIndex();
@@ -4336,7 +4339,8 @@
super.getVolumeGroupMinVolumeIndex_enforcePermission();
synchronized (VolumeStreamState.class) {
if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
- throw new IllegalArgumentException("No volume group for id " + groupId);
+ Log.e(TAG, "No volume group for id " + groupId);
+ return 0;
}
VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
return vgs.getMinIndex();
@@ -4765,6 +4769,8 @@
private void dumpFlags(PrintWriter pw) {
pw.println("\nFun with Flags:");
+ pw.println("\tcom.android.media.audio.as_device_connection_failure:"
+ + asDeviceConnectionFailure());
pw.println("\tandroid.media.audio.autoPublicVolumeApiHardening:"
+ autoPublicVolumeApiHardening());
pw.println("\tandroid.media.audio.Flags.automaticBtDeviceType:"
@@ -8259,11 +8265,21 @@
private static final SparseArray<VolumeGroupState> sVolumeGroupStates = new SparseArray<>();
private void initVolumeGroupStates() {
+ int btScoGroupId = -1;
+ VolumeGroupState voiceCallGroup = null;
for (final AudioVolumeGroup avg : getAudioVolumeGroups()) {
try {
- // if no valid attributes, this volume group is not controllable
- if (ensureValidAttributes(avg)) {
- sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
+ if (ensureValidVolumeGroup(avg)) {
+ final VolumeGroupState vgs = new VolumeGroupState(avg);
+ sVolumeGroupStates.append(avg.getId(), vgs);
+ if (vgs.isVoiceCall()) {
+ voiceCallGroup = vgs;
+ }
+ } else {
+ // invalid volume group will be reported for bt sco group with no other
+ // legacy stream type, we try to replace it in sVolumeGroupStates with the
+ // voice call volume group
+ btScoGroupId = avg.getId();
}
} catch (IllegalArgumentException e) {
// Volume Groups without attributes are not controllable through set/get volume
@@ -8271,10 +8287,15 @@
if (DEBUG_VOL) {
Log.d(TAG, "volume group " + avg.name() + " for internal policy needs");
}
- continue;
}
}
+ if (replaceStreamBtSco() && btScoGroupId >= 0 && voiceCallGroup != null) {
+ // the bt sco group is deprecated, storing the voice call group instead
+ // to keep the code backwards compatible when calling the volume group APIs
+ sVolumeGroupStates.append(btScoGroupId, voiceCallGroup);
+ }
+
// need mSettingsLock for vgs.applyAllVolumes -> vss.setIndex which grabs this lock after
// VSS.class. Locking order needs to be preserved
synchronized (mSettingsLock) {
@@ -8285,7 +8306,15 @@
}
}
- private boolean ensureValidAttributes(AudioVolumeGroup avg) {
+ /**
+ * Returns false if the legacy stream types only contains the deprecated
+ * {@link AudioSystem#STREAM_BLUETOOTH_SCO}.
+ *
+ * @throws IllegalArgumentException if it has more than one non-default {@link AudioAttributes}
+ *
+ * @param avg the volume group to check
+ */
+ private boolean ensureValidVolumeGroup(AudioVolumeGroup avg) {
boolean hasAtLeastOneValidAudioAttributes = avg.getAudioAttributes().stream()
.anyMatch(aa -> !aa.equals(AudioProductStrategy.getDefaultAttributes()));
if (!hasAtLeastOneValidAudioAttributes) {
@@ -8293,10 +8322,11 @@
+ " has no valid audio attributes");
}
if (replaceStreamBtSco()) {
- for (int streamType : avg.getLegacyStreamTypes()) {
- if (streamType == AudioSystem.STREAM_BLUETOOTH_SCO) {
- return false;
- }
+ // if there are multiple legacy stream types associated we can omit stream bt sco
+ // otherwise this is not a valid volume group
+ if (avg.getLegacyStreamTypes().length == 1
+ && avg.getLegacyStreamTypes()[0] == AudioSystem.STREAM_BLUETOOTH_SCO) {
+ return false;
}
}
return true;
@@ -8637,6 +8667,10 @@
return mHasValidStreamType && mPublicStreamType == AudioSystem.STREAM_MUSIC;
}
+ public boolean isVoiceCall() {
+ return mHasValidStreamType && mPublicStreamType == AudioSystem.STREAM_VOICE_CALL;
+ }
+
public void applyAllVolumes(boolean userSwitch) {
String caller = "from vgs";
synchronized (AudioService.VolumeStreamState.class) {
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index 92fd9cb..15c8850 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -14,3 +14,10 @@
description: "This flag controls whether virtual HAL is used for testing instead of TestHal "
bug: "294254230"
}
+
+flag {
+ name: "notify_fingerprint_loe"
+ namespace: "biometrics_framework"
+ description: "This flag controls whether a notification should be sent to notify user when loss of enrollment happens"
+ bug: "351036558"
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
index 53e6bdb..27f9cc8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
@@ -151,6 +151,43 @@
}
/**
+ * Shows a fingerprint notification for loss of enrollment
+ */
+ public static void showFingerprintLoeNotification(@NonNull Context context) {
+ Slog.d(TAG, "Showing fingerprint LOE notification");
+
+ final String name =
+ context.getString(R.string.device_unlock_notification_name);
+ final String title = context.getString(R.string.fingerprint_dangling_notification_title);
+ final String content = context.getString(R.string.fingerprint_loe_notification_msg);
+
+ // Create "Set up" notification action button.
+ final Intent setupIntent =
+ new Intent(BiometricDanglingReceiver.ACTION_FINGERPRINT_RE_ENROLL_LAUNCH);
+ final PendingIntent setupPendingIntent = PendingIntent.getBroadcastAsUser(context, 0,
+ setupIntent, PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT);
+ final String setupText =
+ context.getString(R.string.biometric_dangling_notification_action_set_up);
+ final Notification.Action setupAction = new Notification.Action.Builder(
+ null, setupText, setupPendingIntent).build();
+
+ // Create "Not now" notification action button.
+ final Intent notNowIntent =
+ new Intent(BiometricDanglingReceiver.ACTION_FINGERPRINT_RE_ENROLL_DISMISS);
+ final PendingIntent notNowPendingIntent = PendingIntent.getBroadcastAsUser(context, 0,
+ notNowIntent, PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT);
+ final String notNowText = context.getString(
+ R.string.biometric_dangling_notification_action_not_now);
+ final Notification.Action notNowAction = new Notification.Action.Builder(
+ null, notNowText, notNowPendingIntent).build();
+
+ showNotificationHelper(context, name, title, content, setupPendingIntent, setupAction,
+ notNowAction, Notification.CATEGORY_SYSTEM, FINGERPRINT_RE_ENROLL_CHANNEL,
+ FINGERPRINT_RE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET, false,
+ Notification.FLAG_NO_CLEAR);
+ }
+
+ /**
* Shows a fingerprint bad calibration notification.
*/
public static void showBadCalibrationNotification(@NonNull Context context) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java b/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java
index 7fb27b6..63678aa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java
@@ -57,6 +57,7 @@
protected boolean mInvalidationInProgress;
protected final Context mContext;
protected final File mFile;
+ private boolean mIsInvalidBiometricState = false;
private final Runnable mWriteStateRunnable = this::doWriteStateInternal;
@@ -102,7 +103,7 @@
serializer.endDocument();
destination.finishWrite(out);
} catch (Throwable t) {
- Slog.wtf(TAG, "Failed to write settings, restoring backup", t);
+ Slog.e(TAG, "Failed to write settings, restoring backup", t);
destination.failWrite(out);
throw new IllegalStateException("Failed to write to file: " + mFile.toString(), t);
} finally {
@@ -192,6 +193,29 @@
}
}
+ /**
+ * Return true if the biometric file is correctly read. Otherwise return false.
+ */
+ public boolean isInvalidBiometricState() {
+ return mIsInvalidBiometricState;
+ }
+
+ /**
+ * Delete the file of the biometric state.
+ */
+ public void deleteBiometricFile() {
+ synchronized (this) {
+ if (!mFile.exists()) {
+ return;
+ }
+ if (mFile.delete()) {
+ Slog.i(TAG, mFile + " is deleted successfully");
+ } else {
+ Slog.i(TAG, "Failed to delete " + mFile);
+ }
+ }
+ }
+
private boolean isUnique(String name) {
for (T identifier : mBiometrics) {
if (identifier.getName().equals(name)) {
@@ -218,7 +242,8 @@
try {
in = new FileInputStream(mFile);
} catch (FileNotFoundException fnfe) {
- Slog.i(TAG, "No fingerprint state");
+ Slog.i(TAG, "No fingerprint state", fnfe);
+ mIsInvalidBiometricState = true;
return;
}
try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java
index ebe4679..0b4f640 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java
@@ -33,4 +33,14 @@
CharSequence getUniqueName(Context context, int userId);
void setInvalidationInProgress(Context context, int userId, boolean inProgress);
boolean isInvalidationInProgress(Context context, int userId);
+
+ /**
+ * Return true if the biometric file is correctly read. Otherwise return false.
+ */
+ boolean hasValidBiometricUserState(Context context, int userId);
+
+ /**
+ * Delete the file of the biometric state.
+ */
+ void deleteStateForUser(int userId);
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 69ad152..3b6aeef 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -25,6 +25,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -62,7 +63,7 @@
}
private final ArrayList<UserTemplate> mUnknownHALTemplates = new ArrayList<>();
- private final BiometricUtils<S> mBiometricUtils;
+ protected final BiometricUtils<S> mBiometricUtils;
private final Map<Integer, Long> mAuthenticatorIds;
private final boolean mHasEnrollmentsBeforeStarting;
private BaseClientMonitor mCurrentTask;
@@ -105,6 +106,11 @@
startCleanupUnknownHalTemplates();
}
}
+
+ if (mBiometricUtils.hasValidBiometricUserState(getContext(), getTargetUserId())
+ && Flags.notifyFingerprintLoe()) {
+ handleInvalidBiometricState();
+ }
}
};
@@ -248,4 +254,8 @@
public ArrayList<UserTemplate> getUnknownHALTemplates() {
return mUnknownHALTemplates;
}
+
+ protected void handleInvalidBiometricState() {}
+
+ protected abstract int getModality();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java
index c574478..79285cb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java
@@ -124,6 +124,22 @@
return getStateForUser(context, userId).isInvalidationInProgress();
}
+ @Override
+ public boolean hasValidBiometricUserState(Context context, int userId) {
+ return getStateForUser(context, userId).isInvalidBiometricState();
+ }
+
+ @Override
+ public void deleteStateForUser(int userId) {
+ synchronized (this) {
+ FaceUserState state = mUserStates.get(userId);
+ if (state != null) {
+ state.deleteBiometricFile();
+ mUserStates.delete(userId);
+ }
+ }
+ }
+
private FaceUserState getStateForUser(Context ctx, int userId) {
synchronized (this) {
FaceUserState state = mUserStates.get(userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index e75c6ab..964bf6c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
import android.hardware.face.Face;
import android.os.IBinder;
@@ -77,4 +78,9 @@
FaceUtils.getInstance(getSensorId()).addBiometricForUser(
getContext(), getTargetUserId(), (Face) identifier);
}
+
+ @Override
+ protected int getModality() {
+ return BiometricsProtoEnums.MODALITY_FACE;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java
index 0062d31..b8c06c7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java
@@ -140,6 +140,22 @@
return getStateForUser(context, userId).isInvalidationInProgress();
}
+ @Override
+ public boolean hasValidBiometricUserState(Context context, int userId) {
+ return getStateForUser(context, userId).isInvalidBiometricState();
+ }
+
+ @Override
+ public void deleteStateForUser(int userId) {
+ synchronized (this) {
+ FingerprintUserState state = mUserStates.get(userId);
+ if (state != null) {
+ state.deleteBiometricFile();
+ mUserStates.delete(userId);
+ }
+ }
+ }
+
private FingerprintUserState getStateForUser(Context ctx, int userId) {
synchronized (this) {
FingerprintUserState state = mUserStates.get(userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index 5edc2ca..1fc5179 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -22,9 +22,11 @@
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
+import android.util.Slog;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalCleanupClient;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -42,6 +44,8 @@
public class FingerprintInternalCleanupClient
extends InternalCleanupClient<Fingerprint, AidlSession> {
+ private static final String TAG = "FingerprintInternalCleanupClient";
+
public FingerprintInternalCleanupClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
int userId, @NonNull String owner, int sensorId,
@@ -80,4 +84,16 @@
FingerprintUtils.getInstance(getSensorId()).addBiometricForUser(
getContext(), getTargetUserId(), (Fingerprint) identifier);
}
+
+ @Override
+ public void handleInvalidBiometricState() {
+ Slog.d(TAG, "Invalid fingerprint user state: delete the state.");
+ mBiometricUtils.deleteStateForUser(getTargetUserId());
+ BiometricNotificationUtils.showFingerprintLoeNotification(getContext());
+ }
+
+ @Override
+ protected int getModality() {
+ return BiometricsProtoEnums.MODALITY_FINGERPRINT;
+ }
}
diff --git a/services/core/java/com/android/server/crashrecovery/TEST_MAPPING b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
index 537fb325..615db34 100644
--- a/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
+++ b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
@@ -1,9 +1,4 @@
{
- "presubmit": [
- {
- "name": "CrashRecoveryModuleTests"
- }
- ],
"postsubmit": [
{
"name": "FrameworksMockingServicesTests",
@@ -12,6 +7,9 @@
"include-filter": "com.android.server.RescuePartyTest"
}
]
+ },
+ {
+ "name": "CrashRecoveryModuleTests"
}
]
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 7b5cff7..226bdf5 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -579,6 +579,14 @@
return mCurrentBrightnessMapper.getMode();
}
+ /**
+ * @return The preset for this mapping strategy. Presets are used on devices that allow users
+ * to choose from a set of predefined options in display auto-brightness settings.
+ */
+ public int getPreset() {
+ return mCurrentBrightnessMapper.getPreset();
+ }
+
public boolean isInIdleMode() {
return mCurrentBrightnessMapper.getMode() == AUTO_BRIGHTNESS_MODE_IDLE;
}
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 8405e0a..b0507fb 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -140,10 +140,10 @@
builder.setShortTermModelLowerLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
builder.setShortTermModelUpperLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
return new PhysicalMappingStrategy(builder.build(), nitsRange, brightnessRange,
- autoBrightnessAdjustmentMaxGamma, mode, displayWhiteBalanceController);
+ autoBrightnessAdjustmentMaxGamma, mode, preset, displayWhiteBalanceController);
} else if (isValidMapping(luxLevels, brightnessLevels)) {
return new SimpleMappingStrategy(luxLevels, brightnessLevels,
- autoBrightnessAdjustmentMaxGamma, shortTermModelTimeout, mode);
+ autoBrightnessAdjustmentMaxGamma, shortTermModelTimeout, mode, preset);
} else {
return null;
}
@@ -394,6 +394,12 @@
abstract int getMode();
/**
+ * @return The preset for this mapping strategy. Presets are used on devices that allow users
+ * to choose from a set of predefined options in display auto-brightness settings.
+ */
+ abstract int getPreset();
+
+ /**
* Check if the short term model should be reset given the anchor lux the last
* brightness change was made at and the current ambient lux.
*/
@@ -598,6 +604,8 @@
@AutomaticBrightnessController.AutomaticBrightnessMode
private final int mMode;
+ private final int mPreset;
+
private Spline mSpline;
private float mMaxGamma;
private float mAutoBrightnessAdjustment;
@@ -606,7 +614,8 @@
private long mShortTermModelTimeout;
private SimpleMappingStrategy(float[] lux, float[] brightness, float maxGamma,
- long timeout, @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ long timeout, @AutomaticBrightnessController.AutomaticBrightnessMode int mode,
+ int preset) {
Preconditions.checkArgument(lux.length != 0 && brightness.length != 0,
"Lux and brightness arrays must not be empty!");
Preconditions.checkArgument(lux.length == brightness.length,
@@ -633,6 +642,7 @@
computeSpline();
mShortTermModelTimeout = timeout;
mMode = mode;
+ mPreset = preset;
}
@Override
@@ -766,6 +776,11 @@
}
@Override
+ int getPreset() {
+ return mPreset;
+ }
+
+ @Override
float getUserLux() {
return mUserLux;
}
@@ -837,6 +852,8 @@
@AutomaticBrightnessController.AutomaticBrightnessMode
private final int mMode;
+ private final int mPreset;
+
// Previous short-term models and the times that they were computed stored for debugging
// purposes
private List<Spline> mPreviousBrightnessSplines = new ArrayList<>();
@@ -846,7 +863,7 @@
public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits,
float[] brightness, float maxGamma,
- @AutomaticBrightnessController.AutomaticBrightnessMode int mode,
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset,
@Nullable DisplayWhiteBalanceController displayWhiteBalanceController) {
Preconditions.checkArgument(nits.length != 0 && brightness.length != 0,
@@ -860,6 +877,7 @@
PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, "brightness");
mMode = mode;
+ mPreset = preset;
mMaxGamma = maxGamma;
mAutoBrightnessAdjustment = 0;
mUserLux = INVALID_LUX;
@@ -1073,6 +1091,11 @@
}
@Override
+ int getPreset() {
+ return mPreset;
+ }
+
+ @Override
float getUserLux() {
return mUserLux;
}
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 515e704..8a3e392 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -60,7 +60,7 @@
mModeChangeCallback = modeChangeCallback;
mHdrClamper = hdrClamper;
mNormalBrightnessModeController = normalBrightnessModeController;
- mUseHdrClamper = flags.isHdrClamperEnabled();
+ mUseHdrClamper = flags.isHdrClamperEnabled() && !flags.useNewHdrBrightnessModifier();
mUseNbmController = flags.isNbmControllerEnabled();
if (mUseNbmController) {
mNormalBrightnessModeController.resetNbmData(
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index ed6ed60..cc115f1 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -588,22 +588,43 @@
* <minorVersion>0</minorVersion>
* </usiVersion>
* <evenDimmer enabled="true">
- * <transitionPoint>0.1</transitionPoint>
- *
- * <nits>0.2</nits>
- * <nits>2.0</nits>
- * <nits>500.0</nits>
- * <nits>1000.0</nits>
- *
- * <backlight>0</backlight>
- * <backlight>0.0001</backlight>
- * <backlight>0.5</backlight>
- * <backlight>1.0</backlight>
- *
- * <brightness>0</brightness>
- * <brightness>0.1</brightness>
- * <brightness>0.5</brightness>
- * <brightness>1.0</brightness>
+ * <transitionPoint>0.1</transitionPoint>
+ * <brightnessMapping>
+ * <brightnessPoint>
+ * <nits>0.2</nits>
+ * <backlight>0</backlight>
+ * <brightness>0</brightness>
+ * </brightnessPoint>
+ * <brightnessPoint>
+ * <nits>2.0</nits>
+ * <backlight>0.01</backlight>
+ * <brightness>0.002</brightness>
+ * </brightnessPoint>
+ * <brightnessPoint>
+ * <nits>500.0</nits>
+ * <backlight>0.5</backlight>
+ * <brightness>0.5</brightness>
+ * </brightnessPoint>
+ * <brightnessPoint>
+ * <nits>1000</nits>
+ * <backlight>1.0</backlight>
+ * <brightness>1.0</brightness>
+ * </brightnessPoint>
+ * </brightnessMapping>
+ * <luxToMinimumNitsMap>
+ * <point>
+ * <value>10</value>
+ * <nits>0.3</nits>
+ * </point>
+ * <point>
+ * <value>50</value>
+ * <nits>0.7</nits>
+ * </point>
+ * <point>
+ * <value>100</value>
+ * <nits>1.0</nits>
+ * </point>
+ * </luxToMinimumNitsMap>
* </evenDimmer>
* <screenBrightnessCapForWearBedtimeMode>0.1</screenBrightnessCapForWearBedtimeMode>
* <idleScreenRefreshRateTimeout>
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2cec869..9e905ab 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -722,6 +722,7 @@
if (userSwitching) {
mCurrentUserId = newUserId;
}
+ mDisplayModeDirector.onSwitchUser();
mLogicalDisplayMapper.forEachLocked(logicalDisplay -> {
if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) {
return;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 8b21d98..480c370 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -702,6 +702,17 @@
private void handleOnSwitchUser(@UserIdInt int newUserId, int userSerial, float newBrightness) {
Slog.i(mTag, "Switching user newUserId=" + newUserId + " userSerial=" + userSerial
+ " newBrightness=" + newBrightness);
+
+ if (mAutomaticBrightnessController != null) {
+ int autoBrightnessPreset = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL,
+ UserHandle.USER_CURRENT);
+ if (autoBrightnessPreset != mAutomaticBrightnessController.getPreset()) {
+ setUpAutoBrightness(mContext, mHandler);
+ }
+ }
+
handleBrightnessModeChange();
if (mBrightnessTracker != null) {
mBrightnessTracker.onSwitchUser(newUserId);
@@ -714,6 +725,7 @@
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.resetShortTermModel();
}
+ mBrightnessClamperController.onUserSwitch();
sendUpdatePowerState();
}
@@ -1009,7 +1021,7 @@
if (mFlags.areAutoBrightnessModesEnabled()) {
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_ALS),
- /* notifyForDescendants= */ false, mSettingsObserver, UserHandle.USER_CURRENT);
+ /* notifyForDescendants= */ false, mSettingsObserver, UserHandle.USER_ALL);
}
handleBrightnessModeChange();
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 9324fc1..12c3197 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -71,6 +71,7 @@
private final List<DisplayDeviceDataListener> mDisplayDeviceDataListeners = new ArrayList<>();
private final List<StatefulModifier> mStatefulModifiers = new ArrayList<>();
+ private final List<UserSwitchListener> mUserSwitchListeners = new ArrayList<>();
private ModifiersAggregatedState mModifiersAggregatedState = new ModifiersAggregatedState();
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
@@ -127,6 +128,9 @@
if (m instanceof StatefulModifier s) {
mStatefulModifiers.add(s);
}
+ if (m instanceof UserSwitchListener l) {
+ mUserSwitchListeners.add(l);
+ }
});
mOnPropertiesChangedListener =
properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
@@ -209,6 +213,13 @@
}
/**
+ * Called when the user switches.
+ */
+ public void onUserSwitch() {
+ mUserSwitchListeners.forEach(listener -> listener.onSwitchUser());
+ }
+
+ /**
* Used to dump ClampersController state.
*/
public void dump(PrintWriter writer) {
@@ -466,6 +477,13 @@
}
/**
+ * A clamper/modifier should implement this interface if it reads user-specific settings
+ */
+ interface UserSwitchListener {
+ void onSwitchUser();
+ }
+
+ /**
* StatefulModifiers contribute to AggregatedState, that is used to decide if brightness
* adjustement is needed
*/
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
index 951980a..c3596c3 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
@@ -41,7 +41,8 @@
* Class used to prevent the screen brightness dipping below a certain value, based on current
* lux conditions and user preferred minimum.
*/
-public class BrightnessLowLuxModifier extends BrightnessModifier {
+public class BrightnessLowLuxModifier extends BrightnessModifier implements
+ BrightnessClamperController.UserSwitchListener {
// To enable these logs, run:
// 'adb shell setprop persist.log.tag.BrightnessLowLuxModifier DEBUG && adb reboot'
@@ -81,10 +82,9 @@
*/
@VisibleForTesting
public void recalculateLowerBound() {
- int userId = UserHandle.USER_CURRENT;
float settingNitsLowerBound = Settings.Secure.getFloatForUser(
mContentResolver, Settings.Secure.EVEN_DIMMER_MIN_NITS,
- /* def= */ MIN_NITS_DEFAULT, userId);
+ /* def= */ MIN_NITS_DEFAULT, UserHandle.USER_CURRENT);
boolean isActive = isSettingEnabled()
&& mAmbientLux != BrightnessMappingStrategy.INVALID_LUX;
@@ -190,6 +190,11 @@
}
@Override
+ public void onSwitchUser() {
+ recalculateLowerBound();
+ }
+
+ @Override
public void dump(PrintWriter pw) {
pw.println("BrightnessLowLuxModifier:");
pw.println(" mIsActive=" + mIsActive);
@@ -221,10 +226,10 @@
super(handler);
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.EVEN_DIMMER_MIN_NITS),
- false, this);
+ false, this, UserHandle.USER_ALL);
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.EVEN_DIMMER_ACTIVATED),
- false, this);
+ false, this, UserHandle.USER_ALL);
}
@Override
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java
index 5e44cc3..ae1801c 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java
@@ -31,6 +31,7 @@
import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.config.HdrBrightnessData;
import java.io.PrintWriter;
@@ -99,7 +100,7 @@
mMaxBrightness = mPendingMaxBrightness;
mClamperChangeListener.onChanged();
};
- onDisplayChanged(displayData);
+ mHandler.post(() -> onDisplayChanged(displayData));
}
// Called in DisplayControllerHandler
@@ -120,6 +121,8 @@
stateBuilder.setHdrBrightness(hdrBrightness);
stateBuilder.setCustomAnimationRate(mTransitionRate);
+ stateBuilder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_HDR);
+
// transition rate applied, reset
mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET;
}
@@ -168,10 +171,18 @@
}
}
+ // Called in DisplayControllerHandler
@Override
public void onDisplayChanged(BrightnessClamperController.DisplayDeviceData displayData) {
- mHandler.post(() -> onDisplayChanged(displayData.mDisplayToken, displayData.mWidth,
- displayData.mHeight, displayData.mDisplayDeviceConfig));
+ mDisplayDeviceConfig = displayData.mDisplayDeviceConfig;
+ mScreenSize = (float) displayData.mWidth * displayData.mHeight;
+ HdrBrightnessData data = mDisplayDeviceConfig.getHdrBrightnessData();
+ if (data == null) {
+ unregisterHdrListener();
+ } else {
+ registerHdrListener(displayData.mDisplayToken);
+ }
+ recalculate(data, mMaxDesiredHdrRatio);
}
// Called in DisplayControllerHandler, when any modifier state changes
@@ -215,20 +226,6 @@
}
// Called in DisplayControllerHandler
- private void onDisplayChanged(IBinder displayToken, int width, int height,
- DisplayDeviceConfig config) {
- mDisplayDeviceConfig = config;
- mScreenSize = (float) width * height;
- HdrBrightnessData data = config.getHdrBrightnessData();
- if (data == null) {
- unregisterHdrListener();
- } else {
- registerHdrListener(displayToken);
- }
- recalculate(data, mMaxDesiredHdrRatio);
- }
-
- // Called in DisplayControllerHandler
private void recalculate(@Nullable HdrBrightnessData data, float maxDesiredHdrRatio) {
Mode newMode = recalculateMode(data);
// if HDR mode changed, notify changed
@@ -258,6 +255,10 @@
if (data == null) {
return Mode.NO_HDR;
}
+ // no HDR layer present
+ if (mHdrLayerSize == DEFAULT_HDR_LAYER_SIZE) {
+ return Mode.NO_HDR;
+ }
// HDR layer < minHdr % for Nbm
if (mHdrLayerSize < mScreenSize * data.minimumHdrPercentOfScreenForNbm) {
return Mode.NO_HDR;
diff --git a/services/core/java/com/android/server/display/brightness/clamper/LightSensorController.java b/services/core/java/com/android/server/display/brightness/clamper/LightSensorController.java
index d89dd28..b219cb1 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/LightSensorController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/LightSensorController.java
@@ -62,6 +62,9 @@
private final SensorEventListener mLightSensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
+ if (event.sensor != mRegisteredLightSensor) {
+ return;
+ }
long now = mInjector.getTime();
mAmbientFilter.addValue(TimeUnit.NANOSECONDS.toMillis(event.timestamp),
event.values[0]);
@@ -95,15 +98,13 @@
if (mRegisteredLightSensor == mLightSensor) {
return;
}
+ if (mLightSensor != null) {
+ mSensorManager.registerListener(mLightSensorEventListener,
+ mLightSensor, mLightSensorRate * 1000, mHandler);
+ }
if (mRegisteredLightSensor != null) {
stop();
}
- if (mLightSensor == null) {
- return;
- }
-
- mSensorManager.registerListener(mLightSensorEventListener,
- mLightSensor, mLightSensorRate * 1000, mHandler);
mRegisteredLightSensor = mLightSensor;
if (DEBUG) {
@@ -115,7 +116,7 @@
if (mRegisteredLightSensor == null) {
return;
}
- mSensorManager.unregisterListener(mLightSensorEventListener);
+ mSensorManager.unregisterListener(mLightSensorEventListener, mRegisteredLightSensor);
mRegisteredLightSensor = null;
mAmbientFilter.clear();
mLightSensorListener.onAmbientLuxChange(INVALID_LUX);
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index d610f08..5e471c8 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -121,6 +121,7 @@
private static final int MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED = 6;
private static final int MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED = 7;
private static final int MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED = 8;
+ private static final int MSG_SWITCH_USER = 9;
private final Object mLock = new Object();
private final Context mContext;
@@ -564,6 +565,13 @@
}
/**
+ * Called when the user switches.
+ */
+ public void onSwitchUser() {
+ mHandler.obtainMessage(MSG_SWITCH_USER).sendToTarget();
+ }
+
+ /**
* Print the object's state and debug information into the given stream.
*
* @param pw The stream to dump information to.
@@ -789,6 +797,13 @@
mHbmObserver.onDeviceConfigRefreshRateInHbmHdrChanged(refreshRateInHbmHdr);
break;
}
+
+ case MSG_SWITCH_USER: {
+ synchronized (mLock) {
+ mSettingsObserver.updateRefreshRateSettingLocked();
+ mSettingsObserver.updateModeSwitchingTypeSettingLocked();
+ }
+ }
}
}
}
@@ -1012,10 +1027,10 @@
final ContentResolver cr = mContext.getContentResolver();
mInjector.registerPeakRefreshRateObserver(cr, this);
mInjector.registerMinRefreshRateObserver(cr, this);
- cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this,
- UserHandle.USER_SYSTEM);
- cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/,
- this);
+ cr.registerContentObserver(mLowPowerModeSetting, /* notifyDescendants= */ false, this,
+ UserHandle.USER_ALL);
+ cr.registerContentObserver(mMatchContentFrameRateSetting,
+ /* notifyDescendants= */ false, this, UserHandle.USER_ALL);
mInjector.registerDisplayListener(mDisplayListener, mHandler);
float deviceConfigDefaultPeakRefresh =
@@ -1156,14 +1171,15 @@
float highestRefreshRate = getMaxRefreshRateLocked(displayId);
float minRefreshRate = Settings.System.getFloatForUser(cr,
- Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId());
+ Settings.System.MIN_REFRESH_RATE, 0f, UserHandle.USER_CURRENT);
if (Float.isInfinite(minRefreshRate)) {
// Infinity means that we want the highest possible refresh rate
minRefreshRate = highestRefreshRate;
}
float peakRefreshRate = Settings.System.getFloatForUser(cr,
- Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate, cr.getUserId());
+ Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate,
+ UserHandle.USER_CURRENT);
if (Float.isInfinite(peakRefreshRate)) {
// Infinity means that we want the highest possible refresh rate
peakRefreshRate = highestRefreshRate;
@@ -1234,9 +1250,9 @@
private void updateModeSwitchingTypeSettingLocked() {
final ContentResolver cr = mContext.getContentResolver();
- int switchingType = Settings.Secure.getIntForUser(
- cr, Settings.Secure.MATCH_CONTENT_FRAME_RATE, mModeSwitchingType /*default*/,
- cr.getUserId());
+ int switchingType = Settings.Secure.getIntForUser(cr,
+ Settings.Secure.MATCH_CONTENT_FRAME_RATE, /* default= */ mModeSwitchingType,
+ UserHandle.USER_CURRENT);
if (switchingType != mModeSwitchingType) {
mModeSwitchingType = switchingType;
notifyDesiredDisplayModeSpecsChangedLocked();
@@ -3033,14 +3049,14 @@
public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer) {
cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
- observer, UserHandle.USER_SYSTEM);
+ observer, UserHandle.USER_ALL);
}
@Override
public void registerMinRefreshRateObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer) {
cr.registerContentObserver(MIN_REFRESH_RATE_URI, false /*notifyDescendants*/,
- observer, UserHandle.USER_SYSTEM);
+ observer, UserHandle.USER_ALL);
}
@Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index 3c3bdd5..7746276 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -769,6 +769,7 @@
* @return true if the UI Broadcast type is valid
*/
private static boolean isValidUiBroadcastType(int value) {
+ value = value & 0xFF;
return ((value == 0x00)
|| (value == 0x01)
|| (value == 0x10)
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
index 7f7ae10..58e3452 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
@@ -71,7 +71,20 @@
@Retention(SOURCE)
@Target({METHOD})
@interface PermissionVerified {
+ /**
+ * The name of the permission that is verified, if precisely one permission is required.
+ * If more than one permission is required, specify either {@link #allOf()} instead.
+ *
+ * <p>If specified, {@link #allOf()} must both be {@code null}.</p>
+ */
String value() default "";
+
+ /**
+ * Specifies a list of permission names that are all required.
+ *
+ * <p>If specified, {@link #value()} must both be {@code null}.</p>
+ */
+ String[] allOf() default {};
}
@BinderThread
@@ -132,13 +145,17 @@
void showInputMethodPickerFromClient(IInputMethodClient client, int auxiliarySubtypeMode);
- @PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.WRITE_SECURE_SETTINGS})
void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId);
@PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
boolean isInputMethodPickerShownForTest();
- @PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.WRITE_SECURE_SETTINGS})
void onImeSwitchButtonClickFromSystem(int displayId);
InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId);
@@ -153,7 +170,9 @@
void reportPerceptibleAsync(IBinder windowToken, boolean perceptible);
- @PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.INTERNAL_SYSTEM_WINDOW})
void removeImeSurface(int displayId);
void removeImeSurfaceFromWindowAsync(IBinder windowToken);
@@ -330,13 +349,14 @@
mCallback.showInputMethodPickerFromClient(client, auxiliarySubtypeMode);
}
- @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(allOf = {
+ Manifest.permission.WRITE_SECURE_SETTINGS,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@Override
public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
super.showInputMethodPickerFromSystem_enforcePermission();
mCallback.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
-
}
@EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
@@ -347,7 +367,9 @@
return mCallback.isInputMethodPickerShownForTest();
}
- @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(allOf = {
+ Manifest.permission.WRITE_SECURE_SETTINGS,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@Override
public void onImeSwitchButtonClickFromSystem(int displayId) {
super.onImeSwitchButtonClickFromSystem_enforcePermission();
@@ -382,7 +404,9 @@
mCallback.reportPerceptibleAsync(windowToken, perceptible);
}
- @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @EnforcePermission(allOf = {
+ Manifest.permission.INTERNAL_SYSTEM_WINDOW,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@Override
public void removeImeSurface(int displayId) {
super.removeImeSurface_enforcePermission();
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 0b3f3f0..42a99de 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -121,9 +121,9 @@
@GuardedBy("ImfLock.class")
private boolean mRequestedImeScreenshot;
- /** The window token of the current visible IME layering target overlay. */
+ /** Whether there is a visible IME layering target overlay. */
@GuardedBy("ImfLock.class")
- private IBinder mCurVisibleImeLayeringOverlay;
+ private boolean mHasVisibleImeLayeringOverlay;
/** The window token of the current visible IME input target. */
@GuardedBy("ImfLock.class")
@@ -218,33 +218,36 @@
mPolicy = imePolicy;
mWindowManagerInternal.setInputMethodTargetChangeListener(new ImeTargetChangeListener() {
@Override
- public void onImeTargetOverlayVisibilityChanged(IBinder overlayWindowToken,
+ public void onImeTargetOverlayVisibilityChanged(@NonNull IBinder overlayWindowToken,
@WindowManager.LayoutParams.WindowType int windowType, boolean visible,
boolean removed) {
// Ignoring the starting window since it's ok to cover the IME target
// window in temporary without affecting the IME visibility.
- final var overlay = (visible && !removed && windowType != TYPE_APPLICATION_STARTING)
- ? overlayWindowToken : null;
+ final boolean hasOverlay = visible && !removed
+ && windowType != TYPE_APPLICATION_STARTING;
synchronized (ImfLock.class) {
- mCurVisibleImeLayeringOverlay = overlay;
+ mHasVisibleImeLayeringOverlay = hasOverlay;
}
}
@Override
public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget,
boolean visibleRequested, boolean removed) {
+ final boolean visibleAndNotRemoved = visibleRequested && !removed;
synchronized (ImfLock.class) {
- if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested
- || removed)
- && mCurVisibleImeLayeringOverlay != null) {
+ if (visibleAndNotRemoved) {
+ mCurVisibleImeInputTarget = imeInputTarget;
+ return;
+ }
+ if (mHasVisibleImeLayeringOverlay
+ && mCurVisibleImeInputTarget == imeInputTarget) {
final int reason = SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE;
final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
ImeTracker.ORIGIN_SERVER, reason, false /* fromUser */);
mService.onApplyImeVisibilityFromComputerLocked(imeInputTarget, statsToken,
new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, reason));
}
- mCurVisibleImeInputTarget =
- (visibleRequested && !removed) ? imeInputTarget : null;
+ mCurVisibleImeInputTarget = null;
}
}
});
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 13209d8..dba0465 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -75,13 +75,13 @@
public abstract void setInteractive(boolean interactive);
/**
- * Hides the input methods for all the users, if visible.
+ * Hides the input method for the specified {@code originatingDisplayId}, if visible.
*
* @param reason the reason for hiding the current input method
* @param originatingDisplayId the display ID the request is originated
*/
@ImfLockFree
- public abstract void hideAllInputMethods(@SoftInputShowHideReason int reason,
+ public abstract void hideInputMethod(@SoftInputShowHideReason int reason,
int originatingDisplayId);
/**
@@ -315,7 +315,7 @@
@ImfLockFree
@Override
- public void hideAllInputMethods(@SoftInputShowHideReason int reason,
+ public void hideInputMethod(@SoftInputShowHideReason int reason,
int originatingDisplayId) {
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7ff03c2..5e7d391 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -254,7 +254,7 @@
private @interface MultiUserUnawareField {
}
- private static final int MSG_HIDE_ALL_INPUT_METHODS = 1035;
+ private static final int MSG_HIDE_INPUT_METHOD = 1035;
private static final int MSG_REMOVE_IME_SURFACE = 1060;
private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061;
@@ -997,8 +997,6 @@
Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
ioThread.start();
- SecureSettingsWrapper.setContentResolver(context.getContentResolver());
-
return new InputMethodManagerService(context,
shouldEnableConcurrentMultiUserMode(context), thread.getLooper(),
Handler.createAsync(ioThread.getLooper()),
@@ -1057,7 +1055,6 @@
public void onUserRemoved(UserInfo user) {
// Called directly from UserManagerService. Do not block the calling thread.
final int userId = user.id;
- SecureSettingsWrapper.onUserRemoved(userId);
AdditionalSubtypeMapRepository.remove(userId);
InputMethodSettingsRepository.remove(userId);
mService.mUserDataRepository.remove(userId);
@@ -1129,6 +1126,21 @@
}
});
}
+
+ @Override
+ public void onUserStopped(@NonNull TargetUser user) {
+ final int userId = user.getUserIdentifier();
+ // Called on ActivityManager thread.
+ SecureSettingsWrapper.onUserStopped(userId);
+ mService.mIoHandler.post(() -> {
+ final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
+ final var settings = InputMethodManagerService.queryInputMethodServicesInternal(
+ mService.mContext, userId, additionalSubtypeMap,
+ DirectBootAwareness.AUTO).getMethodMap();
+ InputMethodSettingsRepository.put(userId,
+ InputMethodSettings.create(settings, userId));
+ });
+ }
}
@GuardedBy("ImfLock.class")
@@ -1163,6 +1175,7 @@
mConcurrentMultiUserModeEnabled = concurrentMultiUserModeEnabled;
mContext = context;
mRes = context.getResources();
+ SecureSettingsWrapper.onStart(mContext);
mHandler = Handler.createAsync(uiLooper, this);
mIoHandler = ioHandler;
@@ -1846,13 +1859,6 @@
}
}
- @VisibleForTesting
- void setAttachedClientForTesting(@NonNull ClientState cs) {
- synchronized (ImfLock.class) {
- getUserData(mCurrentUserId).mCurClient = cs;
- }
- }
-
@GuardedBy("ImfLock.class")
private boolean isShowRequestedForCurrentWindow(@UserIdInt int userId) {
final var userData = getUserData(userId);
@@ -3996,7 +4002,9 @@
}
}
- @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @IInputMethodManagerImpl.PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.WRITE_SECURE_SETTINGS})
@Override
public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
// Always call subtype picker, because subtype picker is a superset of input method
@@ -4090,7 +4098,9 @@
}
}
- @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @IInputMethodManagerImpl.PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.WRITE_SECURE_SETTINGS})
@Override
public void onImeSwitchButtonClickFromSystem(int displayId) {
synchronized (ImfLock.class) {
@@ -4432,7 +4442,9 @@
});
}
- @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @IInputMethodManagerImpl.PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.INTERNAL_SYSTEM_WINDOW})
@Override
public void removeImeSurface(int displayId) {
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
@@ -5034,7 +5046,7 @@
return;
}
- if (Flags.imeSwitcherRevamp()) {
+ if (mNewInputMethodSwitcherMenuEnabled) {
if (DEBUG) {
Slog.v(TAG, "Show IME switcher menu,"
+ " showAuxSubtypes=" + showAuxSubtypes
@@ -5066,7 +5078,7 @@
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
- case MSG_HIDE_ALL_INPUT_METHODS: {
+ case MSG_HIDE_INPUT_METHOD: {
@SoftInputShowHideReason final int reason = msg.arg1;
final int originatingDisplayId = msg.arg2;
synchronized (ImfLock.class) {
@@ -5802,10 +5814,10 @@
@ImfLockFree
@Override
- public void hideAllInputMethods(@SoftInputShowHideReason int reason,
+ public void hideInputMethod(@SoftInputShowHideReason int reason,
int originatingDisplayId) {
- mHandler.removeMessages(MSG_HIDE_ALL_INPUT_METHODS);
- mHandler.obtainMessage(MSG_HIDE_ALL_INPUT_METHODS, reason, originatingDisplayId)
+ mHandler.removeMessages(MSG_HIDE_INPUT_METHOD);
+ mHandler.obtainMessage(MSG_HIDE_INPUT_METHOD, reason, originatingDisplayId)
.sendToTarget();
}
diff --git a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
index b3500be..476888e 100644
--- a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
+++ b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
@@ -20,7 +20,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.UserInfo;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -318,13 +321,30 @@
}
/**
- * Called when the system is starting.
+ * Called when {@link InputMethodManagerService} is starting.
*
- * @param contentResolver the {@link ContentResolver} to be used
+ * @param context the {@link Context} to be used.
*/
@AnyThread
- static void setContentResolver(@NonNull ContentResolver contentResolver) {
- sContentResolver = contentResolver;
+ static void onStart(@NonNull Context context) {
+ sContentResolver = context.getContentResolver();
+
+ final int userId = LocalServices.getService(ActivityManagerInternal.class)
+ .getCurrentUserId();
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ putOrGet(userId, createImpl(userManagerInternal, userId));
+
+ userManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ synchronized (sMutationLock) {
+ sUserMap = sUserMap.cloneWithRemoveOrSelf(user.id);
+ }
+ }
+ }
+ );
}
/**
@@ -357,14 +377,19 @@
}
/**
- * Called when a user is being removed.
+ * Called when a user is stopped, which changes the user storage to the locked state again.
*
- * @param userId the ID of the user whose storage is being removed.
+ * @param userId the ID of the user whose storage is being locked again.
*/
@AnyThread
- static void onUserRemoved(@UserIdInt int userId) {
+ static void onUserStopped(@UserIdInt int userId) {
+ final LockedUserImpl lockedUserImpl = new LockedUserImpl(userId, sContentResolver);
synchronized (sMutationLock) {
- sUserMap = sUserMap.cloneWithRemoveOrSelf(userId);
+ final ReaderWriter current = sUserMap.get(userId);
+ if (current == null || current instanceof LockedUserImpl) {
+ return;
+ }
+ sUserMap = sUserMap.cloneWithPutOrSelf(userId, lockedUserImpl);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index f603ff3..c940a9c 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -252,7 +252,9 @@
offload(() -> mInner.showInputMethodPickerFromClient(client, auxiliarySubtypeMode));
}
- @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @IInputMethodManagerImpl.PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.WRITE_SECURE_SETTINGS})
@Override
public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
mInner.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
@@ -264,7 +266,9 @@
return mInner.isInputMethodPickerShownForTest();
}
- @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @IInputMethodManagerImpl.PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.WRITE_SECURE_SETTINGS})
@Override
public void onImeSwitchButtonClickFromSystem(int displayId) {
mInner.onImeSwitchButtonClickFromSystem(displayId);
@@ -298,7 +302,9 @@
mInner.reportPerceptibleAsync(windowToken, perceptible);
}
- @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @IInputMethodManagerImpl.PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.INTERNAL_SYSTEM_WINDOW})
@Override
public void removeImeSurface(int displayId) {
mInner.removeImeSurface(displayId);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4425079..a0d5ea8 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -501,9 +501,9 @@
mPm.setUpCustomResolverActivity(pkg, pkgSetting);
}
- // When upgrading a package, pkgSetting is copied from oldPkgSetting. Clear the app
- // metadata file path for the new package.
- if (oldPkgSetting != null) {
+ // When upgrading a package, clear the app metadata file path for the new package.
+ if (oldPkgSetting != null
+ && oldPkgSetting.getLastUpdateTime() < pkgSetting.getLastUpdateTime()) {
pkgSetting.setAppMetadataFilePath(null);
pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7534bfe..d0706d2 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1158,8 +1158,7 @@
break;
case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME: {
if (mDismissImeOnBackKeyPressed) {
- // TODO(b/308479256): Check if hiding "all" IMEs is OK or not.
- InputMethodManagerInternal.get().hideAllInputMethods(
+ InputMethodManagerInternal.get().hideInputMethod(
SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME, displayId);
} else {
shortPressPowerGoHome();
diff --git a/services/core/java/com/android/server/power/hint/TEST_MAPPING b/services/core/java/com/android/server/power/hint/TEST_MAPPING
index 9988786..5450700 100644
--- a/services/core/java/com/android/server/power/hint/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/hint/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "PerformanceHintTests",
"options": [
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 39954b8..5f41090 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -59,58 +59,61 @@
*/
public void exportAggregatedPowerStats(BatteryUsageStats.Builder batteryUsageStatsBuilder,
long monotonicStartTime, long monotonicEndTime) {
- boolean hasStoredSpans = false;
- long maxEndTime = monotonicStartTime;
- List<PowerStatsSpan.Metadata> spans = mPowerStatsStore.getTableOfContents();
- for (int i = spans.size() - 1; i >= 0; i--) {
- PowerStatsSpan.Metadata metadata = spans.get(i);
- if (!metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) {
- continue;
- }
-
- List<PowerStatsSpan.TimeFrame> timeFrames = metadata.getTimeFrames();
- long spanMinTime = Long.MAX_VALUE;
- long spanMaxTime = Long.MIN_VALUE;
- for (int j = 0; j < timeFrames.size(); j++) {
- PowerStatsSpan.TimeFrame timeFrame = timeFrames.get(j);
- long startMonotonicTime = timeFrame.startMonotonicTime;
- long endMonotonicTime = startMonotonicTime + timeFrame.duration;
- if (startMonotonicTime < spanMinTime) {
- spanMinTime = startMonotonicTime;
+ synchronized (this) {
+ boolean hasStoredSpans = false;
+ long maxEndTime = monotonicStartTime;
+ List<PowerStatsSpan.Metadata> spans = mPowerStatsStore.getTableOfContents();
+ for (int i = spans.size() - 1; i >= 0; i--) {
+ PowerStatsSpan.Metadata metadata = spans.get(i);
+ if (!metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) {
+ continue;
}
- if (endMonotonicTime > spanMaxTime) {
- spanMaxTime = endMonotonicTime;
+
+ List<PowerStatsSpan.TimeFrame> timeFrames = metadata.getTimeFrames();
+ long spanMinTime = Long.MAX_VALUE;
+ long spanMaxTime = Long.MIN_VALUE;
+ for (int j = 0; j < timeFrames.size(); j++) {
+ PowerStatsSpan.TimeFrame timeFrame = timeFrames.get(j);
+ long startMonotonicTime = timeFrame.startMonotonicTime;
+ long endMonotonicTime = startMonotonicTime + timeFrame.duration;
+ if (startMonotonicTime < spanMinTime) {
+ spanMinTime = startMonotonicTime;
+ }
+ if (endMonotonicTime > spanMaxTime) {
+ spanMaxTime = endMonotonicTime;
+ }
+ }
+
+ if (!(spanMinTime >= monotonicStartTime && spanMaxTime < monotonicEndTime)) {
+ continue;
+ }
+
+ if (spanMaxTime > maxEndTime) {
+ maxEndTime = spanMaxTime;
+ }
+
+ PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
+ AggregatedPowerStatsSection.TYPE);
+ if (span == null) {
+ Slog.e(TAG, "Could not read PowerStatsStore section " + metadata);
+ continue;
+ }
+ List<PowerStatsSpan.Section> sections = span.getSections();
+ for (int k = 0; k < sections.size(); k++) {
+ hasStoredSpans = true;
+ PowerStatsSpan.Section section = sections.get(k);
+ populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder,
+ ((AggregatedPowerStatsSection) section).getAggregatedPowerStats());
}
}
- if (!(spanMinTime >= monotonicStartTime && spanMaxTime < monotonicEndTime)) {
- continue;
+ if (!hasStoredSpans
+ || maxEndTime < monotonicEndTime - mBatterySessionTimeSpanSlackMillis) {
+ mPowerStatsAggregator.aggregatePowerStats(maxEndTime, monotonicEndTime,
+ stats -> populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, stats));
}
-
- if (spanMaxTime > maxEndTime) {
- maxEndTime = spanMaxTime;
- }
-
- PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
- AggregatedPowerStatsSection.TYPE);
- if (span == null) {
- Slog.e(TAG, "Could not read PowerStatsStore section " + metadata);
- continue;
- }
- List<PowerStatsSpan.Section> sections = span.getSections();
- for (int k = 0; k < sections.size(); k++) {
- hasStoredSpans = true;
- PowerStatsSpan.Section section = sections.get(k);
- populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder,
- ((AggregatedPowerStatsSection) section).getAggregatedPowerStats());
- }
+ mPowerStatsAggregator.reset();
}
-
- if (!hasStoredSpans || maxEndTime < monotonicEndTime - mBatterySessionTimeSpanSlackMillis) {
- mPowerStatsAggregator.aggregatePowerStats(maxEndTime, monotonicEndTime,
- stats -> populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, stats));
- }
- mPowerStatsAggregator.reset();
}
private void populateBatteryUsageStatsBuilder(
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 85c8900..e9423ce 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -1890,8 +1890,7 @@
enforceStatusBarService();
final long token = Binder.clearCallingIdentity();
try {
- // TODO(b/308479256): Check if hiding "all" IMEs is OK or not.
- InputMethodManagerInternal.get().hideAllInputMethods(
+ InputMethodManagerInternal.get().hideInputMethod(
SoftInputShowHideReason.HIDE_BUBBLES, displayId);
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index f70a3ba..d5bea4a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -676,7 +676,12 @@
final Rect estimateCrop = new Rect(cropHint);
if (!multiCrop()) estimateCrop.scale(1f / options.inSampleSize);
- else estimateCrop.scale(1f / sampleSize);
+ else {
+ estimateCrop.left = (int) Math.floor(estimateCrop.left / sampleSize);
+ estimateCrop.top = (int) Math.floor(estimateCrop.top / sampleSize);
+ estimateCrop.right = (int) Math.ceil(estimateCrop.right / sampleSize);
+ estimateCrop.bottom = (int) Math.ceil(estimateCrop.bottom / sampleSize);
+ }
float hRatio = (float) wpData.mHeight / estimateCrop.height();
final int destHeight = (int) (estimateCrop.height() * hRatio);
final int destWidth = (int) (estimateCrop.width() * hRatio);
@@ -720,7 +725,10 @@
}
if (multiCrop()) {
Slog.v(TAG, " cropHint=" + cropHint);
+ Slog.v(TAG, " estimateCrop=" + estimateCrop);
Slog.v(TAG, " sampleSize=" + sampleSize);
+ Slog.v(TAG, " user defined crops: " + wallpaper.mCropHints);
+ Slog.v(TAG, " all crops: " + defaultCrops);
}
Slog.v(TAG, " targetSize=" + safeWidth + "x" + safeHeight);
Slog.v(TAG, " maxTextureSize=" + GLHelper.getMaxTextureSize());
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 54024e9..02c8a49 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -20,9 +20,11 @@
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -226,6 +228,21 @@
};
}
+ static String balStartModeToString(@BackgroundActivityStartMode int startMode) {
+ return switch (startMode) {
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOWED -> "MODE_BACKGROUND_ACTIVITY_START_ALLOWED";
+ case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED ->
+ "MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED";
+ case MODE_BACKGROUND_ACTIVITY_START_COMPAT -> "MODE_BACKGROUND_ACTIVITY_START_COMPAT";
+ case MODE_BACKGROUND_ACTIVITY_START_DENIED -> "MODE_BACKGROUND_ACTIVITY_START_DENIED";
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS ->
+ "MODE_BACKGROUND_ACTIVITY_START_ALWAYS";
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE ->
+ "MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE";
+ default -> "MODE_BACKGROUND_ACTIVITY_START_ALLOWED(" + startMode + ")";
+ };
+ }
+
@GuardedBy("mService.mGlobalLock")
private final HashMap<Integer, FinishedActivityEntry> mTaskIdToFinishedActivity =
new HashMap<>();
@@ -464,10 +481,6 @@
this.mResultForRealCaller = resultForRealCaller;
}
- public boolean isPendingIntentBalAllowedByPermission() {
- return PendingIntentRecord.isPendingIntentBalAllowedByPermission(mCheckedOptions);
- }
-
public boolean callerExplicitOptInOrAutoOptIn() {
if (mAutoOptInCaller) {
return !callerExplicitOptOut();
@@ -528,6 +541,8 @@
sb.append("; balAllowedByPiCreatorWithHardening: ")
.append(mBalAllowedByPiCreatorWithHardening);
sb.append("; resultIfPiCreatorAllowsBal: ").append(mResultForCaller);
+ sb.append("; callerStartMode: ").append(balStartModeToString(
+ mCheckedOptions.getPendingIntentBackgroundActivityStartMode()));
sb.append("; hasRealCaller: ").append(hasRealCaller());
sb.append("; isCallForResult: ").append(mIsCallForResult);
sb.append("; isPendingIntent: ").append(isPendingIntent());
@@ -553,6 +568,8 @@
}
sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender);
sb.append("; resultIfPiSenderAllowsBal: ").append(mResultForRealCaller);
+ sb.append("; realCallerStartMode: ").append(balStartModeToString(
+ mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()));
}
// features
sb.append("; balImproveRealCallerVisibilityCheck: ")
@@ -949,7 +966,8 @@
}
}
- if (state.isPendingIntentBalAllowedByPermission()
+ if (state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
&& hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) {
return new BalVerdict(BAL_ALLOW_PERMISSION,
/*background*/ false,
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
index 8bd8098..27e6e09 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
@@ -55,8 +55,10 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Implementation of {@link SettingsProvider} that reads the base settings provided in a display
@@ -152,19 +154,35 @@
/**
* Removes display override settings that are no longer associated with active displays.
- * This is necessary because displays can be dynamically added or removed during
- * the system's lifecycle (e.g., user switch, system server restart).
+ * <p>
+ * This cleanup process is essential due to the dynamic nature of displays, which can
+ * be added or removed during various system events such as user switching or
+ * system server restarts.
*
- * @param root The root window container used to obtain the currently active displays.
+ * @param wms the WindowManagerService instance for retrieving all possible {@link DisplayInfo}
+ * for the given logical display.
+ * @param root the root window container used to obtain the currently active displays.
*/
- void removeStaleDisplaySettings(@NonNull RootWindowContainer root) {
+ void removeStaleDisplaySettingsLocked(@NonNull WindowManagerService wms,
+ @NonNull RootWindowContainer root) {
if (!Flags.perUserDisplayWindowSettings()) {
return;
}
final Set<String> displayIdentifiers = new ArraySet<>();
+ final Consumer<DisplayInfo> addDisplayIdentifier =
+ displayInfo -> displayIdentifiers.add(mOverrideSettings.getIdentifier(displayInfo));
root.forAllDisplays(dc -> {
- final String identifier = mOverrideSettings.getIdentifier(dc.getDisplayInfo());
- displayIdentifiers.add(identifier);
+ // Begin with the current display's information. Note that the display layout of the
+ // current device state might not include this display (e.g., external or virtual
+ // displays), resulting in empty possible display info.
+ addDisplayIdentifier.accept(dc.getDisplayInfo());
+
+ // Then, add all possible display information for this display if available.
+ final List<DisplayInfo> displayInfos = wms.getPossibleDisplayInfoLocked(dc.mDisplayId);
+ final int size = displayInfos.size();
+ for (int i = 0; i < size; i++) {
+ addDisplayIdentifier.accept(displayInfos.get(i));
+ }
});
mOverrideSettings.removeStaleDisplaySettings(displayIdentifiers);
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index b496a65..b8869f1 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -439,8 +439,7 @@
final InputMethodManagerInternal inputMethodManagerInternal =
LocalServices.getService(InputMethodManagerInternal.class);
if (inputMethodManagerInternal != null) {
- // TODO(b/308479256): Check if hiding "all" IMEs is OK or not.
- inputMethodManagerInternal.hideAllInputMethods(
+ inputMethodManagerInternal.hideInputMethod(
SoftInputShowHideReason.HIDE_RECENTS_ANIMATION,
mDisplayContent.getDisplayId());
}
diff --git a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
index e3a2065..6e87977 100644
--- a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
+++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
@@ -57,7 +57,7 @@
* Returns, for the given displayId, a list of unique display infos. List contains each
* supported device state.
* <p>List contents are guaranteed to be unique, but returned as a list rather than a set to
- * minimize copies needed to make an iteraable data structure.
+ * minimize copies needed to make an iterable data structure.
*/
public List<DisplayInfo> getPossibleDisplayInfos(int displayId) {
// Update display infos before returning, since any cached values would have been removed
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c83b280..ed0dc3b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2819,7 +2819,21 @@
mClearedTaskForReuse,
mClearedTaskFragmentForPip,
mClearedForReorderActivityToFront,
- calculateMinDimension());
+ calculateMinDimension(),
+ isTopNonFinishingChild());
+ }
+
+ private boolean isTopNonFinishingChild() {
+ final WindowContainer<?> parent = getParent();
+ if (parent == null) {
+ // Either the TaskFragment is not attached or is going to destroy. Return false.
+ return false;
+ }
+ final ActivityRecord topNonFishingActivity = parent.getActivity(ar -> !ar.finishing);
+ // If the parent's top non-finishing activity is this TaskFragment's, it means
+ // this TaskFragment is the top non-finishing container of its parent.
+ return topNonFishingActivity != null && topNonFishingActivity
+ .equals(getTopNonFinishingActivity());
}
/**
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 358adc3..af3ed28 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2185,8 +2185,7 @@
for (int i = mParticipants.size() - 1; i >= 0; --i) {
final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken();
if (wallpaper != null) {
- if (!wallpaper.isVisible() && (wallpaper.isVisibleRequested()
- || (Flags.ensureWallpaperInTransitions() && showWallpaper))) {
+ if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) {
wallpaper.commitVisibility(showWallpaper);
} else if (Flags.ensureWallpaperInTransitions() && wallpaper.isVisible()
&& !showWallpaper && !wallpaper.getDisplayContent().isKeyguardLocked()
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4db62478..13453a6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -118,12 +118,12 @@
import static com.android.server.policy.PhoneWindowManager.TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
-import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
-import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
+import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
import static com.android.server.wm.SensitiveContentPackages.PackageInfo;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
@@ -357,7 +357,6 @@
import com.android.server.power.ShutdownThread;
import com.android.server.utils.PriorityDump;
import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
-import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
@@ -3750,7 +3749,7 @@
}
mCurrentUserId = newUserId;
mDisplayWindowSettingsProvider.setOverrideSettingsForUser(newUserId);
- mDisplayWindowSettingsProvider.removeStaleDisplaySettings(mRoot);
+ mDisplayWindowSettingsProvider.removeStaleDisplaySettingsLocked(this, mRoot);
mPolicy.setCurrentUserLw(newUserId);
mKeyguardDisableHandler.setCurrentUser(newUserId);
@@ -5491,7 +5490,7 @@
mRoot.forAllDisplays(DisplayContent::reconfigureDisplayLocked);
// Per-user display settings may leave outdated settings after user switches, especially
// during reboots starting with the default user without setCurrentUser called.
- mDisplayWindowSettingsProvider.removeStaleDisplaySettings(mRoot);
+ mDisplayWindowSettingsProvider.removeStaleDisplaySettingsLocked(this, mRoot);
}
}
@@ -9390,11 +9389,6 @@
return focusedActivity;
}
- if (!Flags.embeddedActivityBackNavFlag()) {
- // Return if flag is not enabled.
- return focusedActivity;
- }
-
if (!focusedActivity.isEmbedded()) {
// Return if the focused activity is not embedded.
return focusedActivity;
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index efcc23f..8ae4f9a 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -41,6 +41,7 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.Nullable;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -50,6 +51,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.InputBindResult;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.inputmethod.StartInputFlags;
@@ -75,7 +77,7 @@
super.setUp();
synchronized (ImfLock.class) {
mVisibilityApplier = mInputMethodManagerService.getVisibilityApplierLocked();
- mInputMethodManagerService.setAttachedClientForTesting(requireNonNull(
+ setAttachedClientLocked(requireNonNull(
mInputMethodManagerService.getClientStateLocked(mMockInputMethodClient)));
}
}
@@ -168,7 +170,9 @@
// Init a IME target client on the secondary display to show IME.
mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection,
10 /* selfReportedDisplayId */);
- mInputMethodManagerService.setAttachedClientForTesting(null);
+ synchronized (ImfLock.class) {
+ setAttachedClientLocked(null);
+ }
startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE);
final var statsToken = ImeTracker.Token.empty();
@@ -206,7 +210,9 @@
@Test
public void testApplyImeVisibility_hideImeWhenUnbinding() {
- mInputMethodManagerService.setAttachedClientForTesting(null);
+ synchronized (ImfLock.class) {
+ setAttachedClientLocked(null);
+ }
startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE);
ExtendedMockito.spyOn(mVisibilityApplier);
@@ -233,6 +239,11 @@
}
}
+ @GuardedBy("ImfLock.class")
+ private void setAttachedClientLocked(@Nullable ClientState cs) {
+ mInputMethodManagerService.getUserData(mUserId).mCurClient = cs;
+ }
+
private InputBindResult startInputOrWindowGainedFocus(IBinder windowToken, int softInputMode) {
return mInputMethodManagerService.startInputOrWindowGainedFocus(
StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index fb73aff..f3cd0c9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -693,6 +693,21 @@
}
@Test
+ public void testGetPreset() {
+ int preset = Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM;
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS, preset);
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder()
+ .setAutoBrightnessLevels(AUTO_BRIGHTNESS_MODE_DEFAULT, preset, DISPLAY_LEVELS)
+ .setAutoBrightnessLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT, preset, LUX_LEVELS)
+ .build();
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
+ assertEquals(preset, strategy.getPreset());
+ }
+
+ @Test
public void testAutoBrightnessModeAndPreset() {
int mode = AUTO_BRIGHTNESS_MODE_DOZE;
int preset = Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
index d672435..6929690 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
@@ -225,5 +225,37 @@
assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ fun testUserSwitch() {
+ // nits: 0.5 -> backlight 0.01 -> brightness -> 0.05
+ whenever(mockDisplayDeviceConfig.getBacklightFromNits(/* nits= */ 0.5f))
+ .thenReturn(0.01f)
+ whenever(mockDisplayDeviceConfig.getBrightnessFromBacklight(/* backlight = */ 0.01f))
+ .thenReturn(0.05f)
+
+ Settings.Secure.putIntForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID) // off
+ Settings.Secure.putFloatForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
+
+ modifier.recalculateLowerBound()
+
+ assertThat(modifier.isActive).isFalse()
+ assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
+ assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - i.e. off
+
+ Settings.Secure.putIntForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
+ Settings.Secure.putFloatForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.5f, USER_ID)
+ modifier.onSwitchUser()
+
+ assertThat(modifier.isActive).isTrue()
+ assertThat(modifier.brightnessReason).isEqualTo(
+ BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND)
+ assertThat(modifier.brightnessLowerBound).isEqualTo(0.05f)
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt
index f59e127..79b99b5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt
@@ -29,6 +29,7 @@
import com.android.server.display.config.createSensorData
import com.android.server.display.utils.AmbientFilter
import org.junit.Before
+import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
@@ -62,31 +63,35 @@
mockLightSensorListener, mockHandler, testInjector)
}
- fun `does not register light sensor if is not configured`() {
+ @Test
+ fun doesNotRegisterLightSensorIfNotConfigured() {
controller.restart()
verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener)
}
- fun `does not register light sensor if missing`() {
+ @Test
+ fun doesNotRegisterLightSensorIfMissing() {
controller.configure(dummySensorData, DISPLAY_ID)
controller.restart()
verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener)
}
- fun `register light sensor if configured and present`() {
+ @Test
+ fun registerLightSensorIfConfiguredAndPresent() {
testInjector.lightSensor = TestUtils
.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT)
controller.configure(dummySensorData, DISPLAY_ID)
controller.restart()
verify(mockSensorManager).registerListener(any(),
- testInjector.lightSensor, LIGHT_SENSOR_RATE * 1000, mockHandler)
+ eq(testInjector.lightSensor), eq(LIGHT_SENSOR_RATE * 1000), eq(mockHandler))
verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener)
}
- fun `register light sensor once if not changed`() {
+ @Test
+ fun registerLightSensorOnceIfNotChanged() {
testInjector.lightSensor = TestUtils
.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT)
controller.configure(dummySensorData, DISPLAY_ID)
@@ -95,11 +100,12 @@
controller.restart()
verify(mockSensorManager).registerListener(any(),
- testInjector.lightSensor, LIGHT_SENSOR_RATE * 1000, mockHandler)
+ eq(testInjector.lightSensor), eq(LIGHT_SENSOR_RATE * 1000), eq(mockHandler))
verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener)
}
- fun `register new light sensor and unregister old if changed`() {
+ @Test
+ fun registerNewAndUnregisterOldLightSensorIfChanged() {
val lightSensor1 = TestUtils
.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT)
testInjector.lightSensor = lightSensor1
@@ -112,19 +118,21 @@
controller.configure(dummySensorData, DISPLAY_ID)
controller.restart()
- inOrder {
+ inOrder(mockSensorManager, mockAmbientFilter, mockLightSensorListener) {
verify(mockSensorManager).registerListener(any(),
- lightSensor1, LIGHT_SENSOR_RATE * 1000, mockHandler)
- verify(mockSensorManager).unregisterListener(any<SensorEventListener>())
+ eq(lightSensor1), eq(LIGHT_SENSOR_RATE * 1000), eq(mockHandler))
+ verify(mockSensorManager).registerListener(any(),
+ eq(lightSensor2), eq(LIGHT_SENSOR_RATE * 1000), eq(mockHandler))
+ verify(mockSensorManager).unregisterListener(any<SensorEventListener>(),
+ eq(lightSensor1))
verify(mockAmbientFilter).clear()
verify(mockLightSensorListener).onAmbientLuxChange(LightSensorController.INVALID_LUX)
- verify(mockSensorManager).registerListener(any(),
- lightSensor2, LIGHT_SENSOR_RATE * 1000, mockHandler)
}
verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener)
}
- fun `notifies listener on ambient lux change`() {
+ @Test
+ fun notifiesListenerOnAmbientLuxChange() {
val expectedLux = 40f
val eventLux = 50
val eventTime = 60L
@@ -141,7 +149,7 @@
listener.onSensorChanged(TestUtils.createSensorEvent(testInjector.lightSensor,
eventLux, eventTime * 1_000_000))
- inOrder {
+ inOrder(mockAmbientFilter, mockLightSensorListener) {
verify(mockAmbientFilter).addValue(eventTime, eventLux.toFloat())
verify(mockLightSensorListener).onAmbientLuxChange(expectedLux)
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 242d559..62400eb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -1088,6 +1088,21 @@
}
@Test
+ public void testModeSwitching_UserSwitch() {
+ DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
+ assertThat(director.getModeSwitchingType()).isEqualTo(
+ DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+
+ int newModeSwitchingType = DisplayManager.SWITCHING_TYPE_NONE;
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.MATCH_CONTENT_FRAME_RATE, newModeSwitchingType);
+ director.onSwitchUser();
+ waitForIdleSync();
+
+ assertThat(director.getModeSwitchingType()).isEqualTo(newModeSwitchingType);
+ }
+
+ @Test
public void testDefaultDisplayModeIsSelectedIfAvailable() {
final float[] refreshRates = new float[]{24f, 25f, 30f, 60f, 90f};
final int defaultModeId = 3;
@@ -1883,6 +1898,62 @@
}
@Test
+ public void testPeakRefreshRate_UserSwitch() {
+ when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+ .thenReturn(true);
+ DisplayModeDirector director =
+ new DisplayModeDirector(mContext, mHandler, mInjector,
+ mDisplayManagerFlags, mDisplayDeviceConfigProvider);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+ Display.Mode[] modes1 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 130),
+ };
+ Display.Mode[] modes2 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 140),
+ };
+ SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+ supportedModesByDisplay.put(DISPLAY_ID, modes1);
+ supportedModesByDisplay.put(DISPLAY_ID_2, modes2);
+
+ Sensor lightSensor = createLightSensor();
+ SensorManager sensorManager = createMockSensorManager(lightSensor);
+ director.start(sensorManager);
+ director.injectSupportedModesByDisplay(supportedModesByDisplay);
+
+ // Disable Smooth Display
+ setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+ Vote vote1 = director.getVote(DISPLAY_ID,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ Vote vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+ assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+ // Switch user to one that has Smooth Display Enabled
+ Settings.System.putFloat(mContext.getContentResolver(), Settings.System.PEAK_REFRESH_RATE,
+ Float.POSITIVE_INFINITY);
+ director.onSwitchUser();
+ waitForIdleSync();
+
+ vote1 = director.getVote(DISPLAY_ID,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0, /* frameRateHigh= */ 130);
+ assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0, /* frameRateHigh= */ 140);
+ }
+
+ @Test
@Parameters({
"true, true, 60",
"false, true, 50",
@@ -2036,6 +2107,62 @@
}
@Test
+ public void testMinRefreshRate_UserSwitch() {
+ when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+ .thenReturn(true);
+ DisplayModeDirector director =
+ new DisplayModeDirector(mContext, mHandler, mInjector,
+ mDisplayManagerFlags, mDisplayDeviceConfigProvider);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+ Display.Mode[] modes1 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 130),
+ };
+ Display.Mode[] modes2 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 140),
+ };
+ SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+ supportedModesByDisplay.put(DISPLAY_ID, modes1);
+ supportedModesByDisplay.put(DISPLAY_ID_2, modes2);
+
+ Sensor lightSensor = createLightSensor();
+ SensorManager sensorManager = createMockSensorManager(lightSensor);
+ director.start(sensorManager);
+ director.injectSupportedModesByDisplay(supportedModesByDisplay);
+
+ // Disable Force Peak Refresh Rate
+ setMinRefreshRate(0);
+
+ Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ Vote vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+ assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+ // Switch user to one that has Force Peak Refresh Rate enabled
+ Settings.System.putFloat(mContext.getContentResolver(), Settings.System.MIN_REFRESH_RATE,
+ Float.POSITIVE_INFINITY);
+ director.onSwitchUser();
+ waitForIdleSync();
+
+ vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 130,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+ assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 140,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+ }
+
+ @Test
public void testPeakAndMinRefreshRate_FlagEnabled_DisplayWithOneMode() {
when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
.thenReturn(true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 8d0b279..fc28f9e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -266,8 +266,8 @@
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mIntentSender).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any(),
- any(), any());
+ verify(mIntentSender).sendIntent(any(), anyInt(), intentCaptor.capture(), any(),
+ (Bundle) any(), any(), any());
Intent value = intentCaptor.getValue();
assertThat(value.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)).isEqualTo(PACKAGE);
assertThat(value.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)).isEqualTo(
@@ -336,8 +336,8 @@
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mIntentSender).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any(),
- any(), any());
+ verify(mIntentSender).sendIntent(any(), anyInt(), intentCaptor.capture(), any(),
+ (Bundle) any(), any(), any());
Intent value = intentCaptor.getValue();
assertThat(value.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)).isEqualTo(PACKAGE);
assertThat(value.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)).isEqualTo(
diff --git a/services/tests/performancehinttests/TEST_MAPPING b/services/tests/performancehinttests/TEST_MAPPING
index faffe35..fa7b897 100644
--- a/services/tests/performancehinttests/TEST_MAPPING
+++ b/services/tests/performancehinttests/TEST_MAPPING
@@ -1,4 +1,12 @@
{
+ "presubmit": [
+ {
+ "name": "PerformanceHintTests",
+ "options": [
+ {"exclude-annotation": "org.junit.Ignore"}
+ ]
+ }
+ ],
"ravenwood-postsubmit": [
{
"name": "PerformanceHintTestsRavenwood",
@@ -7,13 +15,5 @@
{"exclude-annotation": "android.platform.test.annotations.DisabledOnRavenwood"}
]
}
- ],
- "postsubmit": [
- {
- "name": "PerformanceHintTests",
- "options": [
- {"exclude-annotation": "org.junit.Ignore"}
- ]
- }
]
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 20b9592..1afe12f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -840,6 +840,10 @@
info_a.setComponentName(COMPONENT_NAME);
final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
info_b.setComponentName(new ComponentName("package", "class"));
+ writeStringsToSetting(Set.of(
+ info_a.getComponentName().flattenToString(),
+ info_b.getComponentName().flattenToString()),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
AccessibilityUserState userState = mA11yms.getCurrentUserState();
userState.mInstalledServices.clear();
@@ -858,10 +862,9 @@
userState = mA11yms.getCurrentUserState();
assertThat(userState.mEnabledServices).containsExactly(info_b.getComponentName());
//Assert setting change
- final Set<ComponentName> componentsFromSetting = new ArraySet<>();
- mA11yms.readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- userState.mUserId, componentsFromSetting);
- assertThat(componentsFromSetting).containsExactly(info_b.getComponentName());
+ final Set<String> enabledServices =
+ readStringsFromSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+ assertThat(enabledServices).containsExactly(info_b.getComponentName().flattenToString());
}
@Test
@@ -880,6 +883,10 @@
info_a.getComponentName().flattenToString(),
info_b.getComponentName().flattenToString()),
SOFTWARE);
+ writeStringsToSetting(Set.of(
+ info_a.getComponentName().flattenToString(),
+ info_b.getComponentName().flattenToString()),
+ ShortcutUtils.convertToKey(SOFTWARE));
// despite force stopping both packages, only the first service has the relevant flag,
// so only the first should be removed.
@@ -896,13 +903,53 @@
assertThat(userState.getShortcutTargetsLocked(SOFTWARE)).containsExactly(
info_b.getComponentName().flattenToString());
//Assert setting change
- final Set<String> targetsFromSetting = new ArraySet<>();
- mA11yms.readColonDelimitedSettingToSet(ShortcutUtils.convertToKey(SOFTWARE),
- userState.mUserId, str -> str, targetsFromSetting);
+ final Set<String> targetsFromSetting = readStringsFromSetting(
+ ShortcutUtils.convertToKey(SOFTWARE));
assertThat(targetsFromSetting).containsExactly(info_b.getComponentName().flattenToString());
}
@Test
+ public void testPackagesForceStopped_otherServiceStopped_doesNotRemoveContinuousTarget() {
+ final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
+ info_a.setComponentName(COMPONENT_NAME);
+ info_a.flags = FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+ final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
+ info_b.setComponentName(new ComponentName("package", "class"));
+ writeStringsToSetting(Set.of(
+ info_a.getComponentName().flattenToString(),
+ info_b.getComponentName().flattenToString()),
+ ShortcutUtils.convertToKey(SOFTWARE));
+
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.clear();
+ userState.mInstalledServices.add(info_a);
+ userState.mInstalledServices.add(info_b);
+ userState.updateShortcutTargetsLocked(Set.of(
+ info_a.getComponentName().flattenToString(),
+ info_b.getComponentName().flattenToString()),
+ SOFTWARE);
+
+ // Force stopping a service should not disable unrelated continuous services.
+ synchronized (mA11yms.getLock()) {
+ mA11yms.onPackagesForceStoppedLocked(
+ new String[]{info_b.getComponentName().getPackageName()},
+ userState);
+ }
+
+ //Assert user state change
+ userState = mA11yms.getCurrentUserState();
+ assertThat(userState.getShortcutTargetsLocked(SOFTWARE)).containsExactly(
+ info_a.getComponentName().flattenToString(),
+ info_b.getComponentName().flattenToString());
+ //Assert setting unchanged
+ final Set<String> targetsFromSetting = readStringsFromSetting(
+ ShortcutUtils.convertToKey(SOFTWARE));
+ assertThat(targetsFromSetting).containsExactly(
+ info_a.getComponentName().flattenToString(),
+ info_b.getComponentName().flattenToString());
+ }
+
+ @Test
public void testPackageMonitorScanPackages_scansWithoutHoldingLock() {
setupAccessibilityServiceConnection(0);
final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
@@ -1844,6 +1891,11 @@
return result;
}
+ private void writeStringsToSetting(Set<String> strings, String setting) {
+ mA11yms.persistColonDelimitedSetToSettingLocked(
+ setting, UserHandle.USER_SYSTEM, strings, str -> str);
+ }
+
private void broadcastSettingRestored(String setting, String newValue) {
Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 3ef81fd..60bcecc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -620,7 +620,7 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
- public void testTwoFingerTap_StateIsActivated_shouldInDelegating() {
+ public void testTwoFingerTap_StateIsActivated_shouldInDetecting() {
assumeTrue(isWatch());
enableOneFingerPanning(false);
goFromStateIdleTo(STATE_ACTIVATED);
@@ -629,14 +629,15 @@
send(downEvent());
send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
send(upEvent());
- fastForward(ViewConfiguration.getDoubleTapTimeout());
+ fastForward(mMgh.mDetectingState.mMultiTapMaxDelay);
- assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState);
+ verify(mMgh.getNext(), times(3)).onMotionEvent(any(), any(), anyInt());
+ assertTrue(mMgh.mCurrentState == mMgh.mDetectingState);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
- public void testTwoFingerTap_StateIsIdle_shouldInDelegating() {
+ public void testTwoFingerTap_StateIsIdle_shouldInDetecting() {
assumeTrue(isWatch());
enableOneFingerPanning(false);
goFromStateIdleTo(STATE_IDLE);
@@ -645,9 +646,10 @@
send(downEvent());
send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
send(upEvent());
- fastForward(ViewConfiguration.getDoubleTapTimeout());
+ fastForward(mMgh.mDetectingState.mMultiTapMaxDelay);
- assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState);
+ verify(mMgh.getNext(), times(3)).onMotionEvent(any(), any(), anyInt());
+ assertTrue(mMgh.mCurrentState == mMgh.mDetectingState);
}
@Test
@@ -982,6 +984,53 @@
}
@Test
+ public void testSingleFingerOverscrollAtTopEdge_isWatch_scrollDiagonally_noOverscroll() {
+ assumeTrue(isWatch());
+ goFromStateIdleTo(STATE_SINGLE_PANNING);
+ float centerX =
+ (INITIAL_MAGNIFICATION_BOUNDS.right + INITIAL_MAGNIFICATION_BOUNDS.left) / 2.0f;
+ mFullScreenMagnificationController.setCenter(
+ DISPLAY_0, centerX, INITIAL_MAGNIFICATION_BOUNDS.top, false, 1);
+ final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1;
+ PointF initCoords =
+ new PointF(
+ mFullScreenMagnificationController.getCenterX(DISPLAY_0),
+ mFullScreenMagnificationController.getCenterY(DISPLAY_0));
+ PointF edgeCoords = new PointF(initCoords.x, initCoords.y);
+ // Scroll diagonally towards top-right with a bigger right delta
+ edgeCoords.offset(swipeMinDistance * 2, swipeMinDistance);
+
+ swipeAndHold(initCoords, edgeCoords);
+
+ assertTrue(mMgh.mOverscrollHandler.mOverscrollState == mMgh.OVERSCROLL_NONE);
+ assertTrue(isZoomed());
+ }
+
+ @Test
+ public void
+ testSingleFingerOverscrollAtTopEdge_isWatch_scrollDiagonally_expectedOverscrollState() {
+ assumeTrue(isWatch());
+ goFromStateIdleTo(STATE_SINGLE_PANNING);
+ float centerX =
+ (INITIAL_MAGNIFICATION_BOUNDS.right + INITIAL_MAGNIFICATION_BOUNDS.left) / 2.0f;
+ mFullScreenMagnificationController.setCenter(
+ DISPLAY_0, centerX, INITIAL_MAGNIFICATION_BOUNDS.top, false, 1);
+ final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1;
+ PointF initCoords =
+ new PointF(
+ mFullScreenMagnificationController.getCenterX(DISPLAY_0),
+ mFullScreenMagnificationController.getCenterY(DISPLAY_0));
+ PointF edgeCoords = new PointF(initCoords.x, initCoords.y);
+ // Scroll diagonally towards top-right with a bigger top delta
+ edgeCoords.offset(swipeMinDistance, swipeMinDistance * 2);
+
+ swipeAndHold(initCoords, edgeCoords);
+
+ assertTrue(mMgh.mOverscrollHandler.mOverscrollState == mMgh.OVERSCROLL_VERTICAL_EDGE);
+ assertTrue(isZoomed());
+ }
+
+ @Test
public void testSingleFingerScrollAtEdge_isWatch_noOverscroll() {
assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
@@ -1057,9 +1106,24 @@
assumeTrue(isWatch());
goFromStateIdleTo(STATE_ACTIVATED);
- swipeAndHold();
+ PointF pointer = DEFAULT_POINT;
+ send(downEvent(pointer.x, pointer.y));
+
+ // first move triggers the panning state
+ pointer.offset(100, 100);
fastForward(20);
- swipe(DEFAULT_POINT, new PointF(DEFAULT_X * 2, DEFAULT_Y * 2), /* durationMs= */ 20);
+ send(moveEvent(pointer.x, pointer.y));
+
+ // second move actually pans
+ pointer.offset(100, 100);
+ fastForward(20);
+ send(moveEvent(pointer.x, pointer.y));
+ pointer.offset(100, 100);
+ fastForward(20);
+ send(moveEvent(pointer.x, pointer.y));
+
+ fastForward(20);
+ send(upEvent(pointer.x, pointer.y));
verify(mMockScroller).fling(
/* startX= */ anyInt(),
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java
new file mode 100644
index 0000000..b5a538f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceInventoryTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 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.audio;
+
+import static com.android.media.audio.Flags.asDeviceConnectionFailure;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioDeviceInventoryTest {
+
+ private static final String TAG = "AudioDeviceInventoryTest";
+
+ @Mock private AudioService mMockAudioService;
+ private AudioDeviceInventory mDevInventory;
+ @Spy private AudioDeviceBroker mSpyAudioDeviceBroker;
+ @Spy private AudioSystemAdapter mSpyAudioSystem;
+
+ private SystemServerAdapter mSystemServer;
+
+ private BluetoothDevice mFakeBtDevice;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mMockAudioService = mock(AudioService.class);
+ mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
+ mDevInventory = new AudioDeviceInventory(mSpyAudioSystem);
+ mSystemServer = new NoOpSystemServerAdapter();
+ mSpyAudioDeviceBroker = spy(new AudioDeviceBroker(context, mMockAudioService, mDevInventory,
+ mSystemServer, mSpyAudioSystem));
+ mDevInventory.setDeviceBroker(mSpyAudioDeviceBroker);
+
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ mFakeBtDevice = adapter.getRemoteDevice("00:01:02:03:04:05");
+ }
+
+ @After
+ public void tearDown() throws Exception { }
+
+ /**
+ * test that for DEVICE_OUT_BLUETOOTH_A2DP devices, when the device connects, it's only
+ * added to the connected devices when the connection through AudioSystem is successful
+ * @throws Exception on error
+ */
+ @Test
+ public void testSetDeviceConnectionStateA2dp() throws Exception {
+ Log.i(TAG, "starting testSetDeviceConnectionStateA2dp");
+ assertTrue("collection of connected devices not empty at start",
+ mDevInventory.getConnectedDevices().isEmpty());
+
+ final AudioDeviceAttributes ada = new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, mFakeBtDevice.getAddress());
+ AudioDeviceBroker.BtDeviceInfo btInfo =
+ new AudioDeviceBroker.BtDeviceInfo(mFakeBtDevice, BluetoothProfile.A2DP,
+ BluetoothProfile.STATE_CONNECTED, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ AudioSystem.AUDIO_FORMAT_SBC);
+
+ // test that no device is added when AudioSystem returns AUDIO_STATUS_ERROR
+ // when setDeviceConnectionState is called for the connection
+ // NOTE: for now this is only when flag asDeviceConnectionFailure is true
+ if (asDeviceConnectionFailure()) {
+ when(mSpyAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE,
+ AudioSystem.AUDIO_FORMAT_DEFAULT))
+ .thenReturn(AudioSystem.AUDIO_STATUS_ERROR);
+ runWithBluetoothPrivilegedPermission(
+ () -> mDevInventory.onSetBtActiveDevice(/*btInfo*/ btInfo,
+ /*codec*/ AudioSystem.AUDIO_FORMAT_DEFAULT, AudioManager.STREAM_MUSIC));
+
+ assertEquals(0, mDevInventory.getConnectedDevices().size());
+ }
+
+ // test that the device is added when AudioSystem returns AUDIO_STATUS_OK
+ // when setDeviceConnectionState is called for the connection
+ when(mSpyAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE,
+ AudioSystem.AUDIO_FORMAT_DEFAULT))
+ .thenReturn(AudioSystem.AUDIO_STATUS_OK);
+ runWithBluetoothPrivilegedPermission(
+ () -> mDevInventory.onSetBtActiveDevice(/*btInfo*/ btInfo,
+ /*codec*/ AudioSystem.AUDIO_FORMAT_DEFAULT, AudioManager.STREAM_MUSIC));
+ assertEquals(1, mDevInventory.getConnectedDevices().size());
+ }
+
+ // TODO add test for hearing aid
+
+ // TODO add test for BLE
+
+ /**
+ * Executes a Runnable while holding the BLUETOOTH_PRIVILEGED permission
+ * @param toRunWithPermission the runnable to run with BT privileges
+ */
+ private void runWithBluetoothPrivilegedPermission(Runnable toRunWithPermission) {
+ try {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED);
+ toRunWithPermission.run();
+ } finally {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 3789531..36a7b3d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -1296,6 +1296,11 @@
mFingerprints.add((Fingerprint) identifier);
}
+ @Override
+ protected int getModality() {
+ return 0;
+ }
+
public List<Fingerprint> getFingerprints() {
return mFingerprints;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
index c9482ce..a34e796 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -30,12 +31,16 @@
import android.hardware.fingerprint.Fingerprint;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.TestableContext;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -69,6 +74,10 @@
public final TestableContext mContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock
ISession mSession;
@Mock
@@ -168,6 +177,21 @@
assertThat(mClient.getUnknownHALTemplates()).isEmpty();
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_NOTIFY_FINGERPRINT_LOE)
+ public void invalidBiometricUserState() throws Exception {
+ mClient = createClient();
+
+ final List<Fingerprint> list = new ArrayList<>();
+ doReturn(true).when(mFingerprintUtils)
+ .hasValidBiometricUserState(mContext, 2);
+ doReturn(list).when(mFingerprintUtils).getBiometricsForUser(mContext, 2);
+
+ mClient.start(mCallback);
+ mClient.onEnumerationResult(null, 0);
+ verify(mFingerprintUtils).deleteStateForUser(2);
+ }
+
protected FingerprintInternalCleanupClient createClient() {
final Map<Integer, Long> authenticatorIds = new HashMap<>();
return new FingerprintInternalCleanupClient(mContext, () -> mAidlSession, 2 /* userId */,
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index afa22bc..a159ce3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -72,7 +72,6 @@
import android.window.WindowOnBackInvokedDispatcher;
import com.android.server.LocalServices;
-import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -672,7 +671,6 @@
@Test
public void testBackOnMostRecentWindowInActivityEmbedding() {
- mSetFlagsRule.enableFlags(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG);
final Task task = createTask(mDefaultDisplay);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TaskFragment primaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
index 366e519..6e48818 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
+
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_COMPONENT;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND;
@@ -23,7 +26,6 @@
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_SAW_PERMISSION;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
-import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -58,7 +60,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.quality.Strictness;
import java.lang.reflect.Field;
@@ -134,9 +135,8 @@
ActivityOptions mCheckedOptions = ActivityOptions.makeBasic()
.setPendingIntentCreatorBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
- .setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
class TestableBackgroundActivityStartController extends BackgroundActivityStartController {
private Set<Pair<Integer, Integer>> mBalPermissionUidPidPairs = new HashSet<>();
@@ -175,7 +175,6 @@
when(mService.getAppOpsManager()).thenReturn(mAppOpsManager);
setViaReflection(mService, "mProcessMap", mProcessMap);
- //Mockito.when(mSupervisor.getBackgroundActivityLaunchController()).thenReturn(mController);
setViaReflection(mSupervisor, "mRecentTasks", mRecentTasks);
mController = new TestableBackgroundActivityStartController(mService, mSupervisor);
@@ -397,7 +396,7 @@
// setup state
WindowProcessControllerMap mProcessMap = new WindowProcessControllerMap();
- WindowProcessController otherProcess = Mockito.mock(WindowProcessController.class);
+ WindowProcessController otherProcess = mock(WindowProcessController.class);
mProcessMap.put(callingPid, mCallerApp);
mProcessMap.put(REGULAR_PID_1_1, otherProcess);
setViaReflection(mService, "mProcessMap", mProcessMap);
@@ -516,14 +515,13 @@
BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
- checkedOptions.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+ checkedOptions.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, null,
originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
checkedOptions);
- assertThat(balState.isPendingIntentBalAllowedByPermission()).isTrue();
-
// call
BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender(
balState);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index f110c69..e364264 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -547,7 +547,7 @@
assertThat(balState.callerExplicitOptInOrOut()).isFalse();
assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isTrue();
assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
- assertThat(balState.toString()).contains(
+ assertThat(balState.toString()).startsWith(
"[callingPackage: package.app1; "
+ "callingPackageTargetSdk: -1; "
+ "callingUid: 10001; "
@@ -563,6 +563,7 @@
+ "balAllowedByPiCreator: BSP.ALLOW_BAL; "
+ "balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
+ "resultIfPiCreatorAllowsBal: null; "
+ + "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; "
+ "hasRealCaller: true; "
+ "isCallForResult: false; "
+ "isPendingIntent: false; "
@@ -646,7 +647,7 @@
assertThat(balState.callerExplicitOptInOrOut()).isFalse();
assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isFalse();
assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
- assertThat(balState.toString()).contains(
+ assertThat(balState.toString()).startsWith(
"[callingPackage: package.app1; "
+ "callingPackageTargetSdk: -1; "
+ "callingUid: 10001; "
@@ -662,6 +663,7 @@
+ "balAllowedByPiCreator: BSP.NONE; "
+ "balAllowedByPiCreatorWithHardening: BSP.NONE; "
+ "resultIfPiCreatorAllowsBal: null; "
+ + "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; "
+ "hasRealCaller: true; "
+ "isCallForResult: false; "
+ "isPendingIntent: true; "
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
index 3fcf304..2e0d4d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
@@ -24,12 +24,15 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.eq;
import static org.testng.Assert.assertFalse;
import android.annotation.Nullable;
@@ -58,6 +61,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
+import java.util.List;
import java.util.function.Consumer;
/**
@@ -352,20 +356,58 @@
}
@Test
- public void testRemovesStaleDisplaySettings() {
+ public void testRemovesStaleDisplaySettings_defaultDisplay_removesStaleDisplaySettings() {
assumeTrue(com.android.window.flags.Flags.perUserDisplayWindowSettings());
- final DisplayWindowSettingsProvider provider =
- new DisplayWindowSettingsProvider(mDefaultVendorSettingsStorage,
- mOverrideSettingsStorage);
- final DisplayInfo displayInfo = mSecondaryDisplay.getDisplayInfo();
- updateOverrideSettings(provider, displayInfo, settings -> settings.mForcedDensity = 356);
+ // Write density setting for second display then remove it.
+ final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
+ mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
+ final DisplayInfo secDisplayInfo = mSecondaryDisplay.getDisplayInfo();
+ updateOverrideSettings(provider, secDisplayInfo, setting -> setting.mForcedDensity = 356);
mRootWindowContainer.removeChild(mSecondaryDisplay);
- provider.removeStaleDisplaySettings(mRootWindowContainer);
+ // Write density setting for inner and outer default display.
+ final DisplayInfo innerDisplayInfo = mPrimaryDisplay.getDisplayInfo();
+ final DisplayInfo outerDisplayInfo = new DisplayInfo(secDisplayInfo);
+ outerDisplayInfo.displayId = mPrimaryDisplay.mDisplayId;
+ outerDisplayInfo.uniqueId = "TEST_OUTER_DISPLAY_" + System.currentTimeMillis();
+ updateOverrideSettings(provider, innerDisplayInfo, setting -> setting.mForcedDensity = 490);
+ updateOverrideSettings(provider, outerDisplayInfo, setting -> setting.mForcedDensity = 420);
+ final List<DisplayInfo> possibleDisplayInfos = List.of(innerDisplayInfo, outerDisplayInfo);
+ doReturn(possibleDisplayInfos)
+ .when(mWm).getPossibleDisplayInfoLocked(eq(innerDisplayInfo.displayId));
+
+ provider.removeStaleDisplaySettingsLocked(mWm, mRootWindowContainer);
assertThat(mOverrideSettingsStorage.wasWriteSuccessful()).isTrue();
- assertThat(provider.getOverrideSettingsSize()).isEqualTo(0);
+ assertThat(provider.getOverrideSettingsSize()).isEqualTo(2);
+ assertThat(provider.getOverrideSettings(innerDisplayInfo).mForcedDensity).isEqualTo(490);
+ assertThat(provider.getOverrideSettings(outerDisplayInfo).mForcedDensity).isEqualTo(420);
+ }
+
+ @Test
+ public void testRemovesStaleDisplaySettings_displayNotInLayout_keepsDisplaySettings() {
+ assumeTrue(com.android.window.flags.Flags.perUserDisplayWindowSettings());
+
+ // Write density setting for primary display.
+ final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
+ mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
+ final DisplayInfo primDisplayInfo = mPrimaryDisplay.getDisplayInfo();
+ updateOverrideSettings(provider, primDisplayInfo, setting -> setting.mForcedDensity = 420);
+
+ // Add a virtual display and write density setting for it.
+ final DisplayInfo virtDisplayInfo = new DisplayInfo(primDisplayInfo);
+ virtDisplayInfo.uniqueId = "TEST_VIRTUAL_DISPLAY_" + System.currentTimeMillis();
+ createNewDisplay(virtDisplayInfo);
+ waitUntilHandlersIdle(); // Wait until unfrozen after a display is added.
+ updateOverrideSettings(provider, virtDisplayInfo, setting -> setting.mForcedDensity = 490);
+
+ provider.removeStaleDisplaySettingsLocked(mWm, mRootWindowContainer);
+
+ assertThat(mOverrideSettingsStorage.wasWriteSuccessful()).isTrue();
+ assertThat(provider.getOverrideSettingsSize()).isEqualTo(2);
+ assertThat(provider.getOverrideSettings(primDisplayInfo).mForcedDensity).isEqualTo(420);
+ assertThat(provider.getOverrideSettings(virtDisplayInfo).mForcedDensity).isEqualTo(490);
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 3c247a0..6be1af2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -995,16 +995,14 @@
// The focus should change.
assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
- if (Flags.embeddedActivityBackNavFlag()) {
- // Move focus if the adjacent activity is more recently active.
- doReturn(1L).when(appLeftTop).getLastWindowCreateTime();
- doReturn(2L).when(appRightTop).getLastWindowCreateTime();
- assertTrue(mWm.moveFocusToAdjacentEmbeddedWindow(winLeftTop));
+ // Move focus if the adjacent activity is more recently active.
+ doReturn(1L).when(appLeftTop).getLastWindowCreateTime();
+ doReturn(2L).when(appRightTop).getLastWindowCreateTime();
+ assertTrue(mWm.moveFocusToAdjacentEmbeddedWindow(winLeftTop));
- // Do not move the focus if the adjacent activity is less recently active.
- doReturn(3L).when(appLeftTop).getLastWindowCreateTime();
- assertFalse(mWm.moveFocusToAdjacentEmbeddedWindow(winLeftTop));
- }
+ // Do not move the focus if the adjacent activity is less recently active.
+ doReturn(3L).when(appLeftTop).getLastWindowCreateTime();
+ assertFalse(mWm.moveFocusToAdjacentEmbeddedWindow(winLeftTop));
}
@Test
diff --git a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
index c1a86b3..015e188 100644
--- a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
+++ b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
@@ -18,12 +18,20 @@
import android.view.InputDevice.SOURCE_MOUSE
import android.view.InputDevice.SOURCE_TOUCHSCREEN
+import android.view.InputDevice.SOURCE_STYLUS
+import android.view.InputDevice.SOURCE_TOUCHPAD
+
import android.view.InputEventAssigner
import android.view.KeyEvent
import android.view.MotionEvent
import org.junit.Assert.assertEquals
import org.junit.Test
+sealed class StreamEvent
+private data object Vsync : StreamEvent()
+data class MotionEventData(val action: Int, val source: Int, val id: Int, val expectedId: Int) :
+ StreamEvent()
+
/**
* Create a MotionEvent with the provided action, eventTime, and source
*/
@@ -49,64 +57,164 @@
return KeyEvent(eventTime, eventTime, action, code, repeat)
}
+/**
+ * Check that the correct eventIds are assigned in a stream. The stream consists of motion
+ * events or vsync (processed frame)
+ * Each streamEvent should have unique ids when writing tests
+ * The test passes even if two events get assigned the same eventId, since the mapping is
+ * streamEventId -> motionEventId and streamEvents have unique ids
+ */
+private fun checkEventStream(vararg streamEvents: StreamEvent) {
+ val assigner = InputEventAssigner()
+ var eventTime = 10L
+ // Maps MotionEventData.id to MotionEvent.id
+ // We can't control the event id of the generated motion events but for testing it's easier
+ // to label the events with a custom id for readability
+ val eventIdMap: HashMap<Int, Int> = HashMap()
+ for (streamEvent in streamEvents) {
+ when (streamEvent) {
+ is MotionEventData -> {
+ val event = createMotionEvent(streamEvent.action, eventTime, streamEvent.source)
+ eventIdMap[streamEvent.id] = event.id
+ val eventId = assigner.processEvent(event)
+ assertEquals(eventIdMap[streamEvent.expectedId], eventId)
+ }
+ is Vsync -> assigner.notifyFrameProcessed()
+ }
+ eventTime += 1
+ }
+}
+
class InputEventAssignerTest {
companion object {
private const val TAG = "InputEventAssignerTest"
}
/**
- * A single MOVE event should be assigned to the next available frame.
+ * A single event should be assigned to the next available frame.
*/
@Test
- fun testTouchGesture() {
- val assigner = InputEventAssigner()
- val event = createMotionEvent(MotionEvent.ACTION_MOVE, 10, SOURCE_TOUCHSCREEN)
- val eventId = assigner.processEvent(event)
- assertEquals(event.id, eventId)
+ fun testTouchMove() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN, id = 1, expectedId = 1)
+ )
+ }
+
+ @Test
+ fun testMouseMove() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_MOUSE, id = 1, expectedId = 1)
+ )
+ }
+
+ @Test
+ fun testMouseScroll() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_SCROLL, SOURCE_MOUSE, id = 1, expectedId = 1)
+ )
+ }
+
+ @Test
+ fun testStylusMove() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_STYLUS, id = 1, expectedId = 1)
+ )
+ }
+
+ @Test
+ fun testStylusHover() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_HOVER_MOVE, SOURCE_STYLUS, id = 1, expectedId = 1)
+ )
+ }
+
+ @Test
+ fun testTouchpadMove() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_STYLUS, id = 1, expectedId = 1)
+ )
}
/**
- * DOWN event should be used until a vsync comes in. After vsync, the latest event should be
- * produced.
+ * Test that before a VSYNC the event id generated by input event assigner for move events is
+ * the id of the down event. Move events coming after a VSYNC should be assigned their own event
+ * id
*/
- @Test
- fun testTouchDownWithMove() {
- val assigner = InputEventAssigner()
- val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_TOUCHSCREEN)
- val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_TOUCHSCREEN)
- val move2 = createMotionEvent(MotionEvent.ACTION_MOVE, 13, SOURCE_TOUCHSCREEN)
- val move3 = createMotionEvent(MotionEvent.ACTION_MOVE, 14, SOURCE_TOUCHSCREEN)
- val move4 = createMotionEvent(MotionEvent.ACTION_MOVE, 15, SOURCE_TOUCHSCREEN)
- var eventId = assigner.processEvent(down)
- assertEquals(down.id, eventId)
- eventId = assigner.processEvent(move1)
- assertEquals(down.id, eventId)
- eventId = assigner.processEvent(move2)
- // Even though we already had 2 move events, there was no choreographer callback yet.
- // Therefore, we should still get the id of the down event
- assertEquals(down.id, eventId)
+ private fun testDownAndMove(source: Int) {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_DOWN, source, id = 1, expectedId = 1),
+ MotionEventData(MotionEvent.ACTION_MOVE, source, id = 2, expectedId = 1),
+ Vsync,
+ MotionEventData(MotionEvent.ACTION_MOVE, source, id = 4, expectedId = 4)
+ )
+ }
- // Now send CALLBACK_INPUT to the assigner. It should provide the latest motion event
- assigner.notifyFrameProcessed()
- eventId = assigner.processEvent(move3)
- assertEquals(move3.id, eventId)
- eventId = assigner.processEvent(move4)
- assertEquals(move4.id, eventId)
+ @Test
+ fun testTouchDownAndMove() {
+ testDownAndMove(SOURCE_TOUCHSCREEN)
+ }
+
+ @Test
+ fun testMouseDownAndMove() {
+ testDownAndMove(SOURCE_MOUSE)
+ }
+
+ @Test
+ fun testStylusDownAndMove() {
+ testDownAndMove(SOURCE_STYLUS)
+ }
+
+ @Test
+ fun testTouchpadDownAndMove() {
+ testDownAndMove(SOURCE_TOUCHPAD)
}
/**
- * Similar to the above test, but with SOURCE_MOUSE. Since we don't have down latency
- * concept for non-touchscreens, the latest input event will be used.
+ * After an up event, motion events should be assigned their own event id
*/
@Test
- fun testMouseDownWithMove() {
- val assigner = InputEventAssigner()
- val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_MOUSE)
- val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_MOUSE)
- var eventId = assigner.processEvent(down)
- assertEquals(down.id, eventId)
- eventId = assigner.processEvent(move1)
- assertEquals(move1.id, eventId)
+ fun testMouseDownUpAndScroll() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_MOUSE, id = 1, expectedId = 1),
+ MotionEventData(MotionEvent.ACTION_UP, SOURCE_MOUSE, id = 2, expectedId = 2),
+ MotionEventData(MotionEvent.ACTION_SCROLL, SOURCE_MOUSE, id = 3, expectedId = 3)
+ )
+ }
+
+ /**
+ * After an up event, motion events should be assigned their own event id
+ */
+ @Test
+ fun testStylusDownUpAndHover() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_STYLUS, id = 1, expectedId = 1),
+ MotionEventData(MotionEvent.ACTION_UP, SOURCE_STYLUS, id = 2, expectedId = 2),
+ MotionEventData(MotionEvent.ACTION_HOVER_ENTER, SOURCE_STYLUS, id = 3, expectedId = 3)
+ )
+ }
+
+ /**
+ * After a cancel event, motion events should be assigned their own event id
+ */
+ @Test
+ fun testMouseDownCancelAndScroll() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_MOUSE, id = 1, expectedId = 1),
+ MotionEventData(MotionEvent.ACTION_CANCEL, SOURCE_MOUSE, id = 2, expectedId = 2),
+ MotionEventData(MotionEvent.ACTION_SCROLL, SOURCE_MOUSE, id = 3, expectedId = 3)
+ )
+ }
+
+ /**
+ * After a cancel event, motion events should be assigned their own event id
+ */
+ @Test
+ fun testStylusDownCancelAndHover() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_STYLUS, id = 1, expectedId = 1),
+ MotionEventData(MotionEvent.ACTION_CANCEL, SOURCE_STYLUS, id = 2, expectedId = 2),
+ MotionEventData(MotionEvent.ACTION_HOVER_ENTER, SOURCE_STYLUS, id = 3, expectedId = 3)
+ )
}
/**