Merge "Fix display settings loss: consider all possible DisplayInfo." into main
diff --git a/core/api/current.txt b/core/api/current.txt
index d610f4c..c5a70df 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -34161,6 +34161,7 @@
field public static final int USAGE_CLASS_UNKNOWN = 0; // 0x0
field public static final int USAGE_COMMUNICATION_REQUEST = 65; // 0x41
field public static final int USAGE_HARDWARE_FEEDBACK = 50; // 0x32
+ field @FlaggedApi("android.os.vibrator.vibration_attribute_ime_usage_api") public static final int USAGE_IME_FEEDBACK = 82; // 0x52
field public static final int USAGE_MEDIA = 19; // 0x13
field public static final int USAGE_NOTIFICATION = 49; // 0x31
field public static final int USAGE_PHYSICAL_EMULATION = 34; // 0x22
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/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 19de793..a1ae9da 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -84,6 +84,13 @@
}
flag {
+ namespace: "virtual_devices"
+ name: "enforce_remote_device_opt_out_on_all_virtual_displays"
+ description: "Respect canDisplayOnRemoteDevices on all virtual displays"
+ bug: "338973239"
+}
+
+flag {
namespace: "virtual_devices"
name: "virtual_display_multi_window_mode_support"
description: "Add support for WINDOWING_MODE_MULTI_WINDOW to virtual displays by default"
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/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 6952a09..481e6b5 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -617,7 +617,7 @@
*/
public static final int FLAG_ENABLE_VR_MODE = 0x8000;
/**
- * Bit in {@link #flags} indicating if the activity can be displayed on a remote device.
+ * Bit in {@link #flags} indicating if the activity can be displayed on a virtual display.
* Corresponds to {@link android.R.attr#canDisplayOnRemoteDevices}
* @hide
*/
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 88fbbdd..6882d5c 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -359,3 +359,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "show_different_creation_error_for_unsupported_devices"
+ namespace: "profile_experiences"
+ description: "On private space create error due to child account added/fully managed user show message with link to the Help Center to find out more."
+ bug: "340130375"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index fe14d457..00ce949 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1171,12 +1171,14 @@
}
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_HOVER_ENTER:
// Consume and ignore all touches while stylus is down to prevent
// accidental touches from going to the app while writing.
mPrivOps.setHandwritingSurfaceNotTouchable(false);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_HOVER_EXIT:
// Go back to only consuming stylus events so that the user
// can continue to interact with the app using touch
// when the stylus is not down.
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/os/UserManager.java b/core/java/android/os/UserManager.java
index 3aa42c6..392b6eb 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -6442,7 +6442,11 @@
*/
@UnsupportedAppUsage
public int getUserSerialNumber(@UserIdInt int userId) {
- if (android.multiuser.Flags.cacheUserSerialNumber()) {
+ // Read only flag should is to fix early access to this API
+ // cacheUserSerialNumber to be removed after the
+ // cacheUserSerialNumberReadOnly is fully rolled out
+ if (android.multiuser.Flags.cacheUserSerialNumberReadOnly()
+ || android.multiuser.Flags.cacheUserSerialNumber()) {
// System user serial number is always 0, and it always exists.
// There is no need to call binder for that.
if (userId == UserHandle.USER_SYSTEM) {
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 9df5b85..da863e5 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -16,6 +16,9 @@
package android.os;
+import static android.os.vibrator.Flags.FLAG_VIBRATION_ATTRIBUTE_IME_USAGE_API;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -55,6 +58,7 @@
USAGE_PHYSICAL_EMULATION,
USAGE_RINGTONE,
USAGE_TOUCH,
+ USAGE_IME_FEEDBACK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Usage {}
@@ -136,6 +140,12 @@
*/
public static final int USAGE_ACCESSIBILITY = 0x40 | USAGE_CLASS_FEEDBACK;
/**
+ * Usage value to use for input method editor (IME) haptic feedback.
+ */
+ @FlaggedApi(FLAG_VIBRATION_ATTRIBUTE_IME_USAGE_API)
+ public static final int USAGE_IME_FEEDBACK = 0x50 | USAGE_CLASS_FEEDBACK;
+
+ /**
* Usage value to use for media vibrations, such as music, movie, soundtrack, animations, games,
* or any interactive media that isn't for touch feedback specifically.
*/
@@ -174,7 +184,6 @@
FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF,
FLAG_INVALIDATE_SETTINGS_CACHE,
FLAG_PIPELINED_EFFECT,
- FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE
})
@Retention(RetentionPolicy.SOURCE)
public @interface Flag{}
@@ -228,31 +237,12 @@
public static final int FLAG_PIPELINED_EFFECT = 1 << 3;
/**
- * Flag requesting that this vibration effect to be played without applying the user
- * intensity setting to scale the vibration.
- *
- * <p>The user setting is still applied to enable/disable the vibration, but the vibration
- * effect strength will not be scaled based on the enabled setting value.
- *
- * <p>This is intended to be used on scenarios where the system needs to enforce a specific
- * strength for the vibration effect, regardless of the user preference. Only privileged apps
- * can ignore user settings, and this flag will be ignored otherwise.
- *
- * <p>If you need to bypass the user setting when it's disabling vibrations then this also
- * needs the flag {@link #FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF} to be set.
- *
- * @hide
- */
- public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE = 1 << 4;
-
- /**
* All flags supported by vibrator service, update it when adding new flag.
* @hide
*/
public static final int FLAG_ALL_SUPPORTED =
FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF
- | FLAG_INVALIDATE_SETTINGS_CACHE | FLAG_PIPELINED_EFFECT
- | FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
+ | FLAG_INVALIDATE_SETTINGS_CACHE | FLAG_PIPELINED_EFFECT;
/** Creates a new {@link VibrationAttributes} instance with given usage. */
public static @NonNull VibrationAttributes createForUsage(@Usage int usage) {
@@ -349,6 +339,7 @@
case USAGE_RINGTONE:
return AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
case USAGE_TOUCH:
+ case USAGE_IME_FEEDBACK:
return AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
case USAGE_ALARM:
return AudioAttributes.USAGE_ALARM;
@@ -447,6 +438,8 @@
return "PHYSICAL_EMULATION";
case USAGE_HARDWARE_FEEDBACK:
return "HARDWARE_FEEDBACK";
+ case USAGE_IME_FEEDBACK:
+ return "IME";
default:
return "unknown usage " + usage;
}
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index f6e73b3..a4164e9 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -20,6 +20,7 @@
import static android.os.VibrationAttributes.USAGE_ALARM;
import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_IME_FEEDBACK;
import static android.os.VibrationAttributes.USAGE_MEDIA;
import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
@@ -67,6 +68,8 @@
private final int mDefaultNotificationVibrationIntensity;
@VibrationIntensity
private final int mDefaultRingVibrationIntensity;
+ @VibrationIntensity
+ private final int mDefaultKeyboardVibrationIntensity;
private final boolean mKeyboardVibrationSettingsSupported;
@@ -98,6 +101,8 @@
com.android.internal.R.integer.config_defaultNotificationVibrationIntensity);
mDefaultRingVibrationIntensity = loadDefaultIntensity(resources,
com.android.internal.R.integer.config_defaultRingVibrationIntensity);
+ mDefaultKeyboardVibrationIntensity = loadDefaultIntensity(resources,
+ com.android.internal.R.integer.config_defaultKeyboardVibrationIntensity);
}
@VibrationIntensity
@@ -213,6 +218,9 @@
case USAGE_PHYSICAL_EMULATION:
case USAGE_ACCESSIBILITY:
return mDefaultHapticFeedbackIntensity;
+ case USAGE_IME_FEEDBACK:
+ return isKeyboardVibrationSettingsSupported()
+ ? mDefaultKeyboardVibrationIntensity : mDefaultHapticFeedbackIntensity;
case USAGE_MEDIA:
case USAGE_UNKNOWN:
// fall through
@@ -236,6 +244,7 @@
+ ", mDefaultMediaIntensity=" + mDefaultMediaVibrationIntensity
+ ", mDefaultNotificationIntensity=" + mDefaultNotificationVibrationIntensity
+ ", mDefaultRingIntensity=" + mDefaultRingVibrationIntensity
+ + ", mDefaultKeyboardIntensity=" + mDefaultKeyboardVibrationIntensity
+ ", mKeyboardVibrationSettingsSupported=" + mKeyboardVibrationSettingsSupported
+ "}";
}
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 62b3682..67c3464 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -74,3 +74,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "haptics"
+ name: "vibration_attribute_ime_usage_api"
+ is_exported: true
+ description: "A public API for IME usage vibration attribute"
+ bug: "332661766"
+ metadata {
+ purpose: PURPOSE_FEATURE
+ }
+}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index a78a417..6b1aef7 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -1320,7 +1320,11 @@
// It's possible that a Span is removed when the text covering it is
// deleted, in this case, the original start and end of the span might be
// OOB. So it'll reflow the entire string instead.
- reflow(s, 0, 0, s.length());
+ if (Flags.insertModeCrashUpdateLayoutSpan()) {
+ transformAndReflow(s, 0, s.length());
+ } else {
+ reflow(s, 0, 0, s.length());
+ }
} else {
reflow(s, start, end - start, end - start);
}
@@ -1343,7 +1347,11 @@
// When text is changed, it'll also trigger onSpanChanged. In this case we
// can't determine the updated range in the transformed text. So it'll
// reflow the entire range instead.
- reflow(s, 0, 0, s.length());
+ if (Flags.insertModeCrashUpdateLayoutSpan()) {
+ transformAndReflow(s, 0, s.length());
+ } else {
+ reflow(s, 0, 0, s.length());
+ }
} else {
reflow(s, start, end - start, end - start);
reflow(s, nstart, nend - nstart, nend - nstart);
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 155a3e4..88a1b9c 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -125,6 +125,16 @@
}
flag {
+ name: "insert_mode_crash_update_layout_span"
+ namespace: "text"
+ description: "Fix insert mode crash when the text has UpdateLayout span attached."
+ bug: "355137282"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "icu_bidi_migration"
namespace: "text"
description: "A flag for replacing AndroidBidi with android.icu.text.Bidi."
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_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 725d496..e5a9b6a 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -65,14 +65,6 @@
}
flag {
- name: "defer_display_updates"
- namespace: "windowing_frontend"
- description: "Feature flag for deferring DisplayManager updates to WindowManager if Shell transition is running"
- bug: "259220649"
- is_fixed_read_only: true
-}
-
-flag {
name: "close_to_square_config_includes_status_bar"
namespace: "windowing_frontend"
description: "On close to square display, when necessary, configuration includes status bar"
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/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 2e3dbda..0be33c2 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -3296,8 +3296,8 @@
usually TVs.
<p>Requires permission {@code android.permission.DISABLE_SYSTEM_SOUND_EFFECTS}. -->
<attr name="playHomeTransitionSound" format="boolean"/>
- <!-- Indicates whether the activity can be displayed on a remote device which may or
- may not be running Android. -->
+ <!-- Indicates whether the activity can be displayed on a display that may belong to a
+ remote device which may or may not be running Android. -->
<attr name="canDisplayOnRemoteDevices" format="boolean"/>
<attr name="allowUntrustedActivityEmbedding" />
<attr name="knownActivityEmbeddingCerts" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2afc303..8ed444d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1399,6 +1399,10 @@
Settings.System.RING_VIBRATION_INTENSITY more details on the constant values and
meanings. -->
<integer name="config_defaultRingVibrationIntensity">2</integer>
+ <!-- The default intensity level for keyboard vibrations. Note that this will only be applied
+ on devices where config_keyboardVibrationSettingsSupported is true, otherwise the
+ keyboard vibration will follow config_defaultHapticFeedbackIntensity -->
+ <integer name="config_defaultKeyboardVibrationIntensity">2</integer>
<!-- Whether to use the strict phone number matcher by default. -->
<bool name="config_use_strict_phone_number_comparation">false</bool>
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/symbols.xml b/core/res/res/values/symbols.xml
index bc8c778..cbf3fe7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4223,6 +4223,7 @@
<java-symbol type="integer" name="config_defaultMediaVibrationIntensity" />
<java-symbol type="integer" name="config_defaultNotificationVibrationIntensity" />
<java-symbol type="integer" name="config_defaultRingVibrationIntensity" />
+ <java-symbol type="integer" name="config_defaultKeyboardVibrationIntensity" />
<java-symbol type="bool" name="config_maskMainBuiltInDisplayCutout" />
@@ -5583,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/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/display/AmbientBrightnessDayStatsTest.java b/core/tests/coretests/src/android/hardware/display/AmbientBrightnessDayStatsTest.java
index 0a25bd7..244024d 100644
--- a/core/tests/coretests/src/android/hardware/display/AmbientBrightnessDayStatsTest.java
+++ b/core/tests/coretests/src/android/hardware/display/AmbientBrightnessDayStatsTest.java
@@ -24,8 +24,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/hardware/display/BrightnessConfigurationTest.java b/core/tests/coretests/src/android/hardware/display/BrightnessConfigurationTest.java
index 1c7ab74..9f12e51 100644
--- a/core/tests/coretests/src/android/hardware/display/BrightnessConfigurationTest.java
+++ b/core/tests/coretests/src/android/hardware/display/BrightnessConfigurationTest.java
@@ -25,8 +25,8 @@
import android.util.Pair;
import android.util.Xml;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 969ae8e..5a0dacb 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -31,8 +31,8 @@
import android.view.DisplayInfo;
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/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/text/DynamicLayoutOffsetMappingTest.java b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
index 5ff659b..8a41678 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
@@ -21,11 +21,18 @@
import static com.google.common.truth.Truth.assertThat;
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.text.method.OffsetMapping;
+import android.text.style.UpdateLayout;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.text.flags.Flags;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -36,6 +43,9 @@
private static final int WIDTH = 10000;
private static final TextPaint sTextPaint = new TextPaint();
+ @Rule
+ public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Test
public void textWithOffsetMapping() {
final String text = "abcde";
@@ -120,6 +130,84 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_CRASH_UPDATE_LAYOUT_SPAN)
+ public void textWithOffsetMapping_deletion_withUpdateLayoutSpan() {
+ final String text = "abcdef";
+ final SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+ // UpdateLayout span covers the letter 'd'.
+ spannable.setSpan(new UpdateLayout() {}, 3, 4, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ final CharSequence transformedText =
+ new TestOffsetMapping(spannable, 3, "\n\n");
+
+ final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+ .setAlignment(ALIGN_NORMAL)
+ .setIncludePad(false)
+ .setDisplayText(transformedText)
+ .build();
+
+ // delete character 'c', original text becomes "abdef"
+ spannable.delete(2, 3);
+ assertThat(transformedText.toString()).isEqualTo("ab\n\ndef");
+ assertLineRange(layout, /* lineBreaks */ 0, 3, 4, 7);
+
+ // delete character 'd', original text becomes "abef"
+ spannable.delete(2, 3);
+ assertThat(transformedText.toString()).isEqualTo("ab\n\nef");
+ assertLineRange(layout, /* lineBreaks */ 0, 3, 4, 6);
+
+ // delete "be", original text becomes "af"
+ spannable.delete(1, 3);
+ assertThat(transformedText.toString()).isEqualTo("a\n\nf");
+ assertLineRange(layout, /* lineBreaks */ 0, 2, 3, 4);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_CRASH_UPDATE_LAYOUT_SPAN)
+ public void textWithOffsetMapping_insert_withUpdateLayoutSpan() {
+ final String text = "abcdef";
+ final SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+ final CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n");
+
+ // UpdateLayout span covers the letter 'de'.
+ spannable.setSpan(new UpdateLayout() {}, 3, 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+ .setAlignment(ALIGN_NORMAL)
+ .setIncludePad(false)
+ .setDisplayText(transformedText)
+ .build();
+
+ spannable.insert(3, "x");
+ assertThat(transformedText.toString()).isEqualTo("abcx\n\ndef");
+ assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 9);
+
+ spannable.insert(5, "x");
+ assertThat(transformedText.toString()).isEqualTo("abcx\n\ndxef");
+ assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 10);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_CRASH_UPDATE_LAYOUT_SPAN)
+ public void textWithOffsetMapping_replace_withUpdateLayoutSpan() {
+ final String text = "abcdef";
+ final SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+ final CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n");
+ // UpdateLayout span covers the letter 'de'.
+ spannable.setSpan(new UpdateLayout() {}, 3, 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+ .setAlignment(ALIGN_NORMAL)
+ .setIncludePad(false)
+ .setDisplayText(transformedText)
+ .build();
+
+ spannable.replace(2, 4, "xx");
+ assertThat(transformedText.toString()).isEqualTo("abxx\n\nef");
+ assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 8);
+ }
+
+ @Test
public void textWithOffsetMapping_blockBeforeTextChanged_deletion() {
final String text = "abcdef";
final SpannableStringBuilder spannable = new TestNoBeforeTextChangeSpannableString(text);
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/policy/DecorContextTest.java b/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java
index 7f4e9ad..2f3b7f9 100644
--- a/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java
@@ -36,9 +36,9 @@
import android.view.WindowManagerImpl;
import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index 4921e4a..e037f2a 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -44,8 +44,8 @@
import android.view.WindowManager;
import androidx.test.InstrumentationRegistry;
+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/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..8911d18 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..4bec788 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
@@ -270,13 +270,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 +382,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/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 84d2aad..5f838d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -121,7 +121,7 @@
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
- private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ private val taskRepository: DesktopModeTaskRepository,
private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver,
private val launchAdjacentController: LaunchAdjacentController,
private val recentsTransitionHandler: RecentsTransitionHandler,
@@ -181,7 +181,7 @@
}
private fun onInit() {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
+ logD("onInit")
shellCommandHandler.addDumpCallback(this::dump, this)
shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler, this)
shellController.addExternalInterface(
@@ -190,16 +190,12 @@
this
)
transitions.addHandler(this)
- desktopModeTaskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor)
+ taskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor)
dragToDesktopTransitionHandler.setDragToDesktopStateListener(dragToDesktopStateListener)
recentsTransitionHandler.addTransitionStateListener(
object : RecentsTransitionStateListener {
override fun onAnimationStateChanged(running: Boolean) {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: recents animation state changed running=%b",
- running
- )
+ logV("Recents animation state changed running=%b", running)
recentsAnimationRunning = running
}
}
@@ -227,7 +223,7 @@
/** Returns the transition type for the given remote transition. */
private fun transitionType(remoteTransition: RemoteTransition?): Int {
if (remoteTransition == null) {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: remoteTransition is null")
+ logV("RemoteTransition is null")
return TRANSIT_NONE
}
return TRANSIT_TO_FRONT
@@ -235,7 +231,7 @@
/** Show all tasks, that are part of the desktop, on top of launcher */
fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition? = null) {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
+ logV("showDesktopApps")
val wct = WindowContainerTransaction()
bringDesktopAppsToFront(displayId, wct)
@@ -255,7 +251,7 @@
/** Gets number of visible tasks in [displayId]. */
fun visibleTaskCount(displayId: Int): Int =
- desktopModeTaskRepository.getVisibleTaskCount(displayId)
+ taskRepository.getVisibleTaskCount(displayId)
/** Returns true if any tasks are visible in Desktop Mode. */
fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0
@@ -286,14 +282,8 @@
// Fullscreen case where we move the current focused task.
moveToDesktop(allFocusedTasks[0].taskId, transitionSource = transitionSource)
}
- else -> {
- ProtoLog.w(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: Cannot enter desktop, expected less " +
- "than 3 focused tasks but found %d",
- allFocusedTasks.size
- )
- }
+ else -> logW("Cannot enter desktop, expected < 3 focused tasks, found %d",
+ allFocusedTasks.size)
}
}
}
@@ -317,11 +307,7 @@
transitionSource: DesktopModeTransitionSource,
): Boolean {
recentTasksController?.findTaskInBackground(taskId)?.let {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveToDesktopFromNonRunningTask taskId=%d",
- taskId
- )
+ logV("moveToDesktopFromNonRunningTask with taskId=%d, displayId=%d", taskId)
// TODO(342378842): Instead of using default display, support multiple displays
val taskToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(DEFAULT_DISPLAY, wct, taskId)
@@ -351,18 +337,10 @@
) {
if (DesktopModeFlags.MODALS_POLICY.isEnabled(context)
&& isTopActivityExemptFromDesktopWindowing(context, task)) {
- ProtoLog.w(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: Cannot enter desktop, " +
- "ineligible top activity found."
- )
+ logW("Cannot enter desktop for taskId %d, ineligible top activity found", task.taskId)
return
}
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveToDesktop taskId=%d",
- task.taskId
- )
+ logV("moveToDesktop taskId=%d", task.taskId)
exitSplitIfApplicable(wct, task)
// Bring other apps to front first
val taskToMinimize =
@@ -386,11 +364,7 @@
dragToDesktopValueAnimator: MoveToDesktopAnimator,
taskSurface: SurfaceControl,
) {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: startDragToDesktop taskId=%d",
- taskInfo.taskId
- )
+ logV("startDragToDesktop taskId=%d", taskInfo.taskId)
interactionJankMonitor.begin(taskSurface, context,
CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
@@ -442,10 +416,10 @@
* @param taskId task id of the window that's being closed
*/
fun onDesktopWindowClose(wct: WindowContainerTransaction, displayId: Int, taskId: Int) {
- if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskId)) {
+ if (taskRepository.isOnlyVisibleNonClosingTask(taskId)) {
removeWallpaperActivity(wct)
}
- desktopModeTaskRepository.addClosingTask(displayId, taskId)
+ taskRepository.addClosingTask(displayId, taskId)
}
/** Move a task with given `taskId` to fullscreen */
@@ -464,11 +438,7 @@
/** Move a desktop app to split screen. */
fun moveToSplit(task: RunningTaskInfo) {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveToSplit taskId=%d",
- task.taskId
- )
+ logV( "moveToSplit taskId=%s", task.taskId)
val wct = WindowContainerTransaction()
wct.setBounds(task.token, Rect())
// Rather than set windowing mode to multi-window at task level, set it to
@@ -497,11 +467,7 @@
* [startDragToDesktop].
*/
fun cancelDragToDesktop(task: RunningTaskInfo) {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: cancelDragToDesktop taskId=%d",
- task.taskId
- )
+ logV("cancelDragToDesktop taskId=%d", task.taskId)
dragToDesktopTransitionHandler.cancelDragToDesktopTransition(
DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
)
@@ -512,11 +478,7 @@
position: Point,
transitionSource: DesktopModeTransitionSource
) {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveToFullscreen with animation taskId=%d",
- task.taskId
- )
+ logV("moveToFullscreenWithAnimation taskId=%d", task.taskId)
val wct = WindowContainerTransaction()
addMoveToFullscreenChanges(wct, task)
@@ -540,12 +502,7 @@
/** Move a task to the front */
fun moveTaskToFront(taskInfo: RunningTaskInfo) {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveTaskToFront taskId=%d",
- taskInfo.taskId
- )
-
+ logV("moveTaskToFront taskId=%s", taskInfo.taskId)
val wct = WindowContainerTransaction()
wct.reorder(taskInfo.token, true)
val taskToMinimize = addAndGetMinimizeChangesIfNeeded(taskInfo.displayId, wct, taskInfo)
@@ -571,15 +528,10 @@
fun moveToNextDisplay(taskId: Int) {
val task = shellTaskOrganizer.getRunningTaskInfo(taskId)
if (task == null) {
- ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
+ logW("moveToNextDisplay: taskId=%d not found", taskId)
return
}
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "moveToNextDisplay: taskId=%d taskDisplayId=%d",
- taskId,
- task.displayId
- )
+ logV("moveToNextDisplay: taskId=%d displayId=%d", taskId, task.displayId)
val displayIds = rootTaskDisplayAreaOrganizer.displayIds.sorted()
// Get the first display id that is higher than current task display id
@@ -589,7 +541,7 @@
newDisplayId = displayIds.firstOrNull { displayId -> displayId < task.displayId }
}
if (newDisplayId == null) {
- ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found")
+ logW("moveToNextDisplay: next display not found")
return
}
moveToDisplay(task, newDisplayId)
@@ -601,21 +553,15 @@
* No-op if task is already on that display per [RunningTaskInfo.displayId].
*/
private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "moveToDisplay: taskId=%d displayId=%d",
- task.taskId,
- displayId
- )
-
+ logV("moveToDisplay: taskId=%d displayId=%d", task.taskId, displayId)
if (task.displayId == displayId) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
+ logD("moveToDisplay: task already on display %d", displayId)
return
}
val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
if (displayAreaInfo == null) {
- ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found")
+ logW("moveToDisplay: display not found")
return
}
@@ -652,7 +598,7 @@
// If the task's pre-maximize stable bounds were saved, toggle the task to those bounds.
// Otherwise, toggle to the default bounds.
val taskBoundsBeforeMaximize =
- desktopModeTaskRepository.removeBoundsBeforeMaximize(taskInfo.taskId)
+ taskRepository.removeBoundsBeforeMaximize(taskInfo.taskId)
if (taskBoundsBeforeMaximize != null) {
destinationBounds.set(taskBoundsBeforeMaximize)
} else {
@@ -665,7 +611,7 @@
} else {
// Save current bounds so that task can be restored back to original bounds if necessary
// and toggle to the stable bounds.
- desktopModeTaskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, currentTaskBounds)
+ taskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, currentTaskBounds)
if (taskInfo.isResizeable) {
// if resizable then expand to entire stable bounds (full display minus insets)
@@ -770,12 +716,7 @@
wct: WindowContainerTransaction,
newTaskIdInFront: Int? = null
): RunningTaskInfo? {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: bringDesktopAppsToFront, newTaskIdInFront=%s",
- newTaskIdInFront ?: "null"
- )
-
+ logV("bringDesktopAppsToFront, newTaskId=%d", newTaskIdInFront)
// Move home to front, ensures that we go back home when all desktop windows are closed
moveHomeTask(wct, toTop = true)
@@ -786,7 +727,7 @@
}
val nonMinimizedTasksOrderedFrontToBack =
- desktopModeTaskRepository.getActiveNonMinimizedOrderedTasks(displayId)
+ taskRepository.getActiveNonMinimizedOrderedTasks(displayId)
// If we're adding a new Task we might need to minimize an old one
val taskToMinimize: RunningTaskInfo? =
if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) {
@@ -816,7 +757,7 @@
}
private fun addWallpaperActivity(wct: WindowContainerTransaction) {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: addWallpaper")
+ logV("addWallpaperActivity")
val intent = Intent(context, DesktopWallpaperActivity::class.java)
val options =
ActivityOptions.makeBasic().apply {
@@ -834,8 +775,8 @@
}
private fun removeWallpaperActivity(wct: WindowContainerTransaction) {
- desktopModeTaskRepository.wallpaperActivityToken?.let { token ->
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: removeWallpaper")
+ taskRepository.wallpaperActivityToken?.let { token ->
+ logV("removeWallpaperActivity")
wct.removeTask(token)
}
}
@@ -873,11 +814,7 @@
transition: IBinder,
request: TransitionRequestInfo
): WindowContainerTransaction? {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: handleRequest request=%s",
- request
- )
+ logV("handleRequest request=%s", request)
// Check if we should skip handling this transition
var reason = ""
val triggerTask = request.triggerTask
@@ -915,11 +852,7 @@
}
if (!shouldHandleRequest) {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: skipping handleRequest reason=%s",
- reason
- )
+ logV("skipping handleRequest reason=%s", reason)
return null
}
@@ -939,11 +872,7 @@
}
}
}
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: handleRequest result=%s",
- result ?: "null"
- )
+ logV("handleRequest result=%s", result)
return result
}
@@ -977,25 +906,20 @@
task: RunningTaskInfo,
transition: IBinder
): WindowContainerTransaction? {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
+ logV("handleFreeformTaskLaunch")
if (keyguardManager.isKeyguardLocked) {
// Do NOT handle freeform task launch when locked.
// It will be launched in fullscreen windowing mode (Details: b/160925539)
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked")
+ logV("skip keyguard is locked")
return null
}
val wct = WindowContainerTransaction()
if (!isDesktopModeShowing(task.displayId)) {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: bring desktop tasks to front on transition" +
- " taskId=%d",
- task.taskId
- )
+ logD("Bring desktop tasks to front on transition=taskId=%d", task.taskId)
// We are outside of desktop mode and already existing desktop task is being launched.
// We should make this task go to fullscreen instead of freeform. Note that this means
// any re-launch of a freeform window outside of desktop will be in fullscreen.
- if (desktopModeTaskRepository.isActiveTask(task.taskId)) {
+ if (taskRepository.isActiveTask(task.taskId)) {
addMoveToFullscreenChanges(wct, task)
return wct
}
@@ -1020,14 +944,9 @@
task: RunningTaskInfo,
transition: IBinder
): WindowContainerTransaction? {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
+ logV("handleFullscreenTaskLaunch")
if (isDesktopModeShowing(task.displayId)) {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: switch fullscreen task to freeform on transition" +
- " taskId=%d",
- task.taskId
- )
+ logD("Switch fullscreen task to freeform on transition: taskId=%d", task.taskId)
return WindowContainerTransaction().also { wct ->
addMoveToDesktopChanges(wct, task)
// In some launches home task is moved behind new task being launched. Make sure
@@ -1054,17 +973,17 @@
/** Handle task closing by removing wallpaper activity if it's the last active task */
private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleTaskClosing")
+ logV("handleTaskClosing")
val wct = WindowContainerTransaction()
- if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(task.taskId)
- && desktopModeTaskRepository.wallpaperActivityToken != null) {
+ if (taskRepository.isOnlyVisibleNonClosingTask(task.taskId)
+ && taskRepository.wallpaperActivityToken != null) {
// Remove wallpaper activity when the last active task is removed
removeWallpaperActivity(wct)
}
- desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)
+ taskRepository.addClosingTask(task.displayId, task.taskId)
// If a CLOSE or TO_BACK is triggered on a desktop task, remove the task.
if (Flags.enableDesktopWindowingBackNavigation() &&
- desktopModeTaskRepository.isVisibleTask(task.taskId)) {
+ taskRepository.isVisibleTask(task.taskId)) {
wct.removeTask(task.token)
}
return if (wct.isEmpty) null else wct
@@ -1095,7 +1014,7 @@
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
- val activeTasks = desktopModeTaskRepository
+ val activeTasks = taskRepository
.getActiveNonMinimizedOrderedTasks(taskInfo.displayId)
activeTasks.firstOrNull()?.let { activeTask ->
shellTaskOrganizer.getRunningTaskInfo(activeTask)?.let {
@@ -1132,7 +1051,7 @@
if (useDesktopOverrideDensity()) {
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
}
- if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) {
+ if (taskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) {
// Remove wallpaper activity when leaving desktop mode
removeWallpaperActivity(wct)
}
@@ -1151,7 +1070,7 @@
// The task's density may have been overridden in freeform; revert it here as we don't
// want it overridden in multi-window.
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
- if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) {
+ if (taskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) {
// Remove wallpaper activity when leaving desktop mode
removeWallpaperActivity(wct)
}
@@ -1387,12 +1306,12 @@
/** Update the exclusion region for a specified task */
fun onExclusionRegionChanged(taskId: Int, exclusionRegion: Region) {
- desktopModeTaskRepository.updateTaskExclusionRegions(taskId, exclusionRegion)
+ taskRepository.updateTaskExclusionRegions(taskId, exclusionRegion)
}
/** Remove a previously tracked exclusion region for a specified task. */
fun removeExclusionRegionForTask(taskId: Int) {
- desktopModeTaskRepository.removeExclusionRegion(taskId)
+ taskRepository.removeExclusionRegion(taskId)
}
/**
@@ -1402,7 +1321,7 @@
* @param callbackExecutor the executor to call the listener on.
*/
fun addVisibleTasksListener(listener: VisibleTasksListener, callbackExecutor: Executor) {
- desktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor)
+ taskRepository.addVisibleTasksListener(listener, callbackExecutor)
}
/**
@@ -1412,7 +1331,7 @@
* @param callbackExecutor the executor to call the listener on.
*/
fun setTaskRegionListener(listener: Consumer<Region>, callbackExecutor: Executor) {
- desktopModeTaskRepository.setExclusionRegionListener(listener, callbackExecutor)
+ taskRepository.setExclusionRegionListener(listener, callbackExecutor)
}
override fun onUnhandledDrag(
@@ -1430,7 +1349,7 @@
if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent)) {
// TODO(b/320797628): Should only return early if there is an existing running task, and
// notify the user as well. But for now, just ignore the drop.
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "Dropped intent does not support multi-instance")
+ logV("Dropped intent does not support multi-instance")
return false
}
@@ -1462,7 +1381,7 @@
private fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopTasksController")
- desktopModeTaskRepository.dump(pw, innerPrefix)
+ taskRepository.dump(pw, innerPrefix)
}
/** The interface for calls from outside the shell, within the host process. */
@@ -1537,12 +1456,12 @@
SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>(
controller,
{ c ->
- c.desktopModeTaskRepository.addVisibleTasksListener(
+ c.taskRepository.addVisibleTasksListener(
listener,
c.mainExecutor
)
},
- { c -> c.desktopModeTaskRepository.removeVisibleTasksListener(listener) }
+ { c -> c.taskRepository.removeVisibleTasksListener(listener) }
)
}
@@ -1569,10 +1488,7 @@
}
override fun hideStashedDesktopApps(displayId: Int) {
- ProtoLog.w(
- WM_SHELL_DESKTOP_MODE,
- "IDesktopModeImpl: hideStashedDesktopApps is deprecated"
- )
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: hideStashedDesktopApps is deprecated")
}
override fun getVisibleTaskCount(displayId: Int): Int {
@@ -1596,11 +1512,7 @@
}
override fun setTaskListener(listener: IDesktopTaskListener?) {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "IDesktopModeImpl: set task listener=%s",
- listener ?: "null"
- )
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: set task listener=%s", listener)
executeRemoteCallWithTaskPermission(controller, "setTaskListener") { _ ->
listener?.let { remoteListener.register(it) } ?: remoteListener.unregister()
}
@@ -1613,10 +1525,22 @@
}
}
+ private fun logV(msg: String, vararg arguments: Any?) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+ private fun logD(msg: String, vararg arguments: Any?) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+ private fun logW(msg: String, vararg arguments: Any?) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
companion object {
@JvmField
val DESKTOP_MODE_INITIAL_BOUNDS_SCALE =
SystemProperties.getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
+
+ private const val TAG = "DesktopTasksController"
}
/** The positions on a screen that a task can snap to. */
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/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..9dc3401 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,42 @@
}
@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/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 6ad4b31..3146318 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -675,6 +675,24 @@
assertThat(tiles!!.size).isEqualTo(3)
}
+ @Test
+ fun changeInPackagesTiles_doesntTriggerUserChange_logged() =
+ testScope.runTest(USER_INFO_0) {
+ val specs =
+ listOf(
+ TileSpec.create("a"),
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ runCurrent()
+ // Settled on the same list of tiles.
+ assertThat(underTest.currentTilesSpecs).isEqualTo(specs)
+
+ installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
+ runCurrent()
+
+ verify(logger, never()).logTileUserChanged(TileSpec.create("a"), 0)
+ }
+
private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
this.state = state
this.label = label
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/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index af755d3..58719fe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -24,6 +24,9 @@
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -77,11 +80,16 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
@@ -100,6 +108,7 @@
import androidx.compose.ui.util.fastFirstOrNull
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.zIndex
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
@@ -405,7 +414,7 @@
Row(Modifier.fillMaxWidth()) {
StartSidePanel(
onSearchQueryChanged = onSearchQueryChanged,
- modifier = Modifier.fillMaxWidth(fraction = 0.32f),
+ modifier = Modifier.width(200.dp),
categories = categories,
onKeyboardSettingsClicked = onKeyboardSettingsClicked,
selectedCategory = selectedCategoryType,
@@ -462,7 +471,18 @@
@Composable
private fun ShortcutView(modifier: Modifier, searchQuery: String, shortcut: Shortcut) {
- Row(modifier) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val isFocused by interactionSource.collectIsFocusedAsState()
+ Row(
+ modifier
+ .focusable(interactionSource = interactionSource)
+ .outlineFocusModifier(
+ isFocused = isFocused,
+ focusColor = MaterialTheme.colorScheme.secondary,
+ padding = 8.dp,
+ cornerRadius = 16.dp
+ )
+ ) {
Row(
modifier = Modifier.width(128.dp).align(Alignment.CenterVertically),
horizontalArrangement = Arrangement.spacedBy(16.dp),
@@ -670,10 +690,23 @@
colors: NavigationDrawerItemColors =
NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent),
) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val isFocused by interactionSource.collectIsFocusedAsState()
+
Surface(
selected = selected,
onClick = onClick,
- modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 64.dp).fillMaxWidth(),
+ modifier =
+ Modifier.semantics { role = Role.Tab }
+ .heightIn(min = 64.dp)
+ .fillMaxWidth()
+ .focusable(interactionSource = interactionSource)
+ .outlineFocusModifier(
+ isFocused = isFocused,
+ focusColor = MaterialTheme.colorScheme.secondary,
+ padding = 2.dp,
+ cornerRadius = 33.dp
+ ),
shape = RoundedCornerShape(28.dp),
color = colors.containerColor(selected).value,
) {
@@ -697,6 +730,39 @@
}
}
+private fun Modifier.outlineFocusModifier(
+ isFocused: Boolean,
+ focusColor: Color,
+ padding: Dp,
+ cornerRadius: Dp
+): Modifier {
+ if (isFocused) {
+ return this.drawWithContent {
+ val focusOutline =
+ Rect(Offset.Zero, size).let {
+ if (padding > 0.dp) {
+ it.inflate(padding.toPx())
+ } else {
+ it.deflate(padding.unaryMinus().toPx())
+ }
+ }
+ drawContent()
+ drawRoundRect(
+ color = focusColor,
+ style = Stroke(width = 3.dp.toPx()),
+ topLeft = focusOutline.topLeft,
+ size = focusOutline.size,
+ cornerRadius = CornerRadius(cornerRadius.toPx())
+ )
+ }
+ // Increasing Z-Index so focus outline is drawn on top of "selected" category
+ // background.
+ .zIndex(1f)
+ } else {
+ return this
+ }
+}
+
@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun TitleBar() {
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/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 97b5e87..02379e6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -46,7 +46,7 @@
import com.android.systemui.retail.data.repository.RetailModeRepository
import com.android.systemui.settings.UserTracker
import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.kotlin.pairwiseBy
import dagger.Lazy
import java.io.PrintWriter
import javax.inject.Inject
@@ -63,7 +63,6 @@
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -169,17 +168,19 @@
private val userAndTiles =
currentUser
.flatMapLatest { userId ->
- tileSpecRepository.tilesSpecs(userId).map { UserAndTiles(userId, it) }
+ val currentTiles = tileSpecRepository.tilesSpecs(userId)
+ val installedComponents =
+ installedTilesComponentRepository.getInstalledTilesComponents(userId)
+ currentTiles.combine(installedComponents) { tiles, components ->
+ UserTilesAndComponents(userId, tiles, components)
+ }
}
.distinctUntilChanged()
- .pairwise(UserAndTiles(-1, emptyList()))
+ .pairwiseBy(UserTilesAndComponents(-1, emptyList(), emptySet())) { prev, new ->
+ DataWithUserChange(data = new, userChange = prev.userId != new.userId)
+ }
.flowOn(backgroundDispatcher)
- private val installedPackagesWithTiles =
- currentUser.flatMapLatest {
- installedTilesComponentRepository.getInstalledTilesComponents(it)
- }
-
private val minTiles: Int
get() =
if (retailModeRepository.inRetailMode) {
@@ -194,7 +195,6 @@
}
}
- @OptIn(ExperimentalCoroutinesApi::class)
private fun startTileCollection() {
scope.launch {
launch {
@@ -205,95 +205,82 @@
}
launch(backgroundDispatcher) {
- userAndTiles
- .combine(installedPackagesWithTiles) { usersAndTiles, packages ->
- Data(
- usersAndTiles.previousValue,
- usersAndTiles.newValue,
- packages,
- )
- }
- .collectLatest {
- val newTileList = it.newData.tiles
- val userChanged = it.oldData.userId != it.newData.userId
- val newUser = it.newData.userId
- val components = it.installedComponents
+ userAndTiles.collectLatest {
+ val newUser = it.userId
+ val newTileList = it.tiles
+ val components = it.installedComponents
+ val userChanged = it.userChange
- // Destroy all tiles that are not in the new set
- specsToTiles
- .filter {
- it.key !in newTileList && it.value is TileOrNotInstalled.Tile
- }
- .forEach { entry ->
- logger.logTileDestroyed(
- entry.key,
- if (userChanged) {
- QSPipelineLogger.TileDestroyedReason
- .TILE_NOT_PRESENT_IN_NEW_USER
- } else {
- QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
- }
- )
- (entry.value as TileOrNotInstalled.Tile).tile.destroy()
- }
- // MutableMap will keep the insertion order
- val newTileMap = mutableMapOf<TileSpec, TileOrNotInstalled>()
-
- newTileList.forEach { tileSpec ->
- if (tileSpec !in newTileMap) {
- if (
- tileSpec is TileSpec.CustomTileSpec &&
- tileSpec.componentName !in components
- ) {
- newTileMap[tileSpec] = TileOrNotInstalled.NotInstalled
+ // Destroy all tiles that are not in the new set
+ specsToTiles
+ .filter { it.key !in newTileList && it.value is TileOrNotInstalled.Tile }
+ .forEach { entry ->
+ logger.logTileDestroyed(
+ entry.key,
+ if (userChanged) {
+ QSPipelineLogger.TileDestroyedReason
+ .TILE_NOT_PRESENT_IN_NEW_USER
} else {
- // Create tile here will never try to create a CustomTile that
- // is not installed
- val newTile =
- if (tileSpec in specsToTiles) {
- processExistingTile(
- tileSpec,
- specsToTiles.getValue(tileSpec),
- userChanged,
- newUser
- )
- ?: createTile(tileSpec)
- } else {
- createTile(tileSpec)
- }
- if (newTile != null) {
- newTileMap[tileSpec] = TileOrNotInstalled.Tile(newTile)
+ QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
+ }
+ )
+ (entry.value as TileOrNotInstalled.Tile).tile.destroy()
+ }
+ // MutableMap will keep the insertion order
+ val newTileMap = mutableMapOf<TileSpec, TileOrNotInstalled>()
+
+ newTileList.forEach { tileSpec ->
+ if (tileSpec !in newTileMap) {
+ if (
+ tileSpec is TileSpec.CustomTileSpec &&
+ tileSpec.componentName !in components
+ ) {
+ newTileMap[tileSpec] = TileOrNotInstalled.NotInstalled
+ } else {
+ // Create tile here will never try to create a CustomTile that
+ // is not installed
+ val newTile =
+ if (tileSpec in specsToTiles) {
+ processExistingTile(
+ tileSpec,
+ specsToTiles.getValue(tileSpec),
+ userChanged,
+ newUser
+ ) ?: createTile(tileSpec)
+ } else {
+ createTile(tileSpec)
}
+ if (newTile != null) {
+ newTileMap[tileSpec] = TileOrNotInstalled.Tile(newTile)
}
}
}
-
- val resolvedSpecs = newTileMap.keys.toList()
- specsToTiles.clear()
- specsToTiles.putAll(newTileMap)
- val newResolvedTiles =
- newTileMap
- .filter { it.value is TileOrNotInstalled.Tile }
- .map {
- TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile)
- }
-
- _currentSpecsAndTiles.value = newResolvedTiles
- logger.logTilesNotInstalled(
- newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys,
- newUser
- )
- if (newResolvedTiles.size < minTiles) {
- // We ended up with not enough tiles (some may be not installed).
- // Prepend the default set of tiles
- launch { tileSpecRepository.prependDefault(currentUser.value) }
- } else if (resolvedSpecs != newTileList) {
- // There were some tiles that couldn't be created. Change the value in
- // the
- // repository
- launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
- }
}
+
+ val resolvedSpecs = newTileMap.keys.toList()
+ specsToTiles.clear()
+ specsToTiles.putAll(newTileMap)
+ val newResolvedTiles =
+ newTileMap
+ .filter { it.value is TileOrNotInstalled.Tile }
+ .map { TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile) }
+
+ _currentSpecsAndTiles.value = newResolvedTiles
+ logger.logTilesNotInstalled(
+ newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys,
+ newUser
+ )
+ if (newResolvedTiles.size < minTiles) {
+ // We ended up with not enough tiles (some may be not installed).
+ // Prepend the default set of tiles
+ launch { tileSpecRepository.prependDefault(currentUser.value) }
+ } else if (resolvedSpecs != newTileList) {
+ // There were some tiles that couldn't be created. Change the value in
+ // the
+ // repository
+ launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
+ }
+ }
}
}
}
@@ -362,8 +349,7 @@
newQSTileFactory.get().createTile(spec.spec)
} else {
null
- }
- ?: tileFactory.createTile(spec.spec)
+ } ?: tileFactory.createTile(spec.spec)
}
if (tile == null) {
logger.logTileNotFoundInFactory(spec)
@@ -436,15 +422,25 @@
@JvmInline value class Tile(val tile: QSTile) : TileOrNotInstalled
}
-
- private data class UserAndTiles(
- val userId: Int,
- val tiles: List<TileSpec>,
- )
-
- private data class Data(
- val oldData: UserAndTiles,
- val newData: UserAndTiles,
- val installedComponents: Set<ComponentName>,
- )
}
+
+private data class UserTilesAndComponents(
+ val userId: Int,
+ val tiles: List<TileSpec>,
+ val installedComponents: Set<ComponentName>
+)
+
+private data class DataWithUserChange(
+ val userId: Int,
+ val tiles: List<TileSpec>,
+ val installedComponents: Set<ComponentName>,
+ val userChange: Boolean,
+)
+
+private fun DataWithUserChange(data: UserTilesAndComponents, userChange: Boolean) =
+ DataWithUserChange(
+ data.userId,
+ data.tiles,
+ data.installedComponents,
+ userChange,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index d9546ec..1750347 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -106,7 +106,8 @@
@Override
@MainThread
public void onManagedProfileRemoved() {
- mHost.removeTile(getTileSpec());
+ // No OP as this may race with the user change in CurrentTilesInteractor.
+ // If the tile needs to be removed, AutoAdd (or AutoTileManager) will take care of that.
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
index e9e9d8b..cdcefdb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
@@ -22,12 +22,15 @@
import com.android.internal.logging.InstanceId
import com.android.systemui.qs.pipeline.shared.TileSpec
-data class QSTileConfig(
+data class QSTileConfig
+@JvmOverloads
+constructor(
val tileSpec: TileSpec,
val uiConfig: QSTileUIConfig,
val instanceId: InstanceId,
val metricsSpec: String = tileSpec.spec,
val policy: QSTilePolicy = QSTilePolicy.NoRestrictions,
+ val autoRemoveOnUnavailable: Boolean = true,
)
/**
@@ -38,6 +41,7 @@
val iconRes: Int
@DrawableRes get
+
val labelRes: Int
@StringRes get
@@ -48,6 +52,7 @@
data object Empty : QSTileUIConfig {
override val iconRes: Int
get() = Resources.ID_NULL
+
override val labelRes: Int
get() = Resources.ID_NULL
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index 2cdcc24..c6f9ae8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -70,7 +70,7 @@
applicationScope.launch {
launch {
qsTileViewModel.isAvailable.collectIndexed { index, isAvailable ->
- if (!isAvailable) {
+ if (!isAvailable && qsTileViewModel.config.autoRemoveOnUnavailable) {
qsHost.removeTile(tileSpec)
}
// qsTileViewModel.isAvailable flow often starts with isAvailable == true.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index ac2a0d8..1e0e597a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -20,10 +20,14 @@
import android.os.UserHandle
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.server.notification.Flags.screenshareNotificationHiding
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.DynamicPrivacyController
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -32,27 +36,33 @@
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import dagger.Binds
import dagger.Module
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.launch
@Module(includes = [PrivateSensitiveContentCoordinatorModule::class])
interface SensitiveContentCoordinatorModule
@Module
interface PrivateSensitiveContentCoordinatorModule {
- @Binds
- fun bindCoordinator(impl: SensitiveContentCoordinatorImpl): SensitiveContentCoordinator
+ @Binds fun bindCoordinator(impl: SensitiveContentCoordinatorImpl): SensitiveContentCoordinator
}
/** Coordinates re-inflation and post-processing of sensitive notification content. */
interface SensitiveContentCoordinator : Coordinator
@CoordinatorScope
-class SensitiveContentCoordinatorImpl @Inject constructor(
+class SensitiveContentCoordinatorImpl
+@Inject
+constructor(
private val dynamicPrivacyController: DynamicPrivacyController,
private val lockscreenUserManager: NotificationLockscreenUserManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@@ -61,45 +71,85 @@
private val selectedUserInteractor: SelectedUserInteractor,
private val sensitiveNotificationProtectionController:
SensitiveNotificationProtectionController,
-) : Invalidator("SensitiveContentInvalidator"),
- SensitiveContentCoordinator,
- DynamicPrivacyController.Listener,
- OnBeforeRenderListListener {
- private val onSensitiveStateChanged = Runnable() {
- invalidateList("onSensitiveStateChanged")
- }
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val sceneInteractor: SceneInteractor,
+ @Application private val scope: CoroutineScope,
+) :
+ Invalidator("SensitiveContentInvalidator"),
+ SensitiveContentCoordinator,
+ DynamicPrivacyController.Listener,
+ OnBeforeRenderListListener {
+ private var inTransitionFromLockedToGone = false
- private val screenshareSecretFilter = object : NotifFilter("ScreenshareSecretFilter") {
- val NotificationEntry.isSecret
- get() = channel?.lockscreenVisibility == Notification.VISIBILITY_SECRET ||
- sbn.notification?.visibility == Notification.VISIBILITY_SECRET
- override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean {
- return screenshareNotificationHiding() &&
- sensitiveNotificationProtectionController.isSensitiveStateActive &&
- entry.isSecret
+ private val onSensitiveStateChanged = Runnable() { invalidateList("onSensitiveStateChanged") }
+
+ private val screenshareSecretFilter =
+ object : NotifFilter("ScreenshareSecretFilter") {
+ val NotificationEntry.isSecret
+ get() =
+ channel?.lockscreenVisibility == Notification.VISIBILITY_SECRET ||
+ sbn.notification?.visibility == Notification.VISIBILITY_SECRET
+
+ override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean {
+ return screenshareNotificationHiding() &&
+ sensitiveNotificationProtectionController.isSensitiveStateActive &&
+ entry.isSecret
+ }
}
- }
override fun attach(pipeline: NotifPipeline) {
dynamicPrivacyController.addListener(this)
if (screenshareNotificationHiding()) {
- sensitiveNotificationProtectionController
- .registerSensitiveStateListener(onSensitiveStateChanged)
+ sensitiveNotificationProtectionController.registerSensitiveStateListener(
+ onSensitiveStateChanged
+ )
}
pipeline.addOnBeforeRenderListListener(this)
pipeline.addPreRenderInvalidator(this)
if (screenshareNotificationHiding()) {
pipeline.addFinalizeFilter(screenshareSecretFilter)
}
+
+ if (SceneContainerFlag.isEnabled) {
+ scope.launch {
+ sceneInteractor.transitionState
+ .mapNotNull {
+ val transitioningToGone = it.isTransitioning(to = Scenes.Gone)
+ val deviceEntered = deviceEntryInteractor.isDeviceEntered.value
+ when {
+ transitioningToGone && !deviceEntered -> true
+ !transitioningToGone -> false
+ else -> null
+ }
+ }
+ .distinctUntilChanged()
+ .collect {
+ inTransitionFromLockedToGone = it
+ invalidateList("inTransitionFromLockedToGoneChanged")
+ }
+ }
+ }
}
override fun onDynamicPrivacyChanged(): Unit = invalidateList("onDynamicPrivacyChanged")
+ private val isKeyguardGoingAway: Boolean
+ get() {
+ if (SceneContainerFlag.isEnabled) {
+ return inTransitionFromLockedToGone
+ } else {
+ return keyguardStateController.isKeyguardGoingAway
+ }
+ }
+
override fun onBeforeRenderList(entries: List<ListEntry>) {
- if (keyguardStateController.isKeyguardGoingAway ||
+ if (
+ isKeyguardGoingAway ||
statusBarStateController.state == StatusBarState.KEYGUARD &&
- keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
- selectedUserInteractor.getSelectedUserId())) {
+ keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
+ selectedUserInteractor.getSelectedUserId()
+ )
+ ) {
// don't update yet if:
// - the keyguard is currently going away
// - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash)
@@ -109,35 +159,40 @@
return
}
- val isSensitiveContentProtectionActive = screenshareNotificationHiding() &&
- sensitiveNotificationProtectionController.isSensitiveStateActive
+ val isSensitiveContentProtectionActive =
+ screenshareNotificationHiding() &&
+ sensitiveNotificationProtectionController.isSensitiveStateActive
val currentUserId = lockscreenUserManager.currentUserId
val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
- val deviceSensitive = (devicePublic &&
+ val deviceSensitive =
+ (devicePublic &&
!lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)) ||
isSensitiveContentProtectionActive
val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked
for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) {
val notifUserId = entry.sbn.user.identifier
- val userLockscreen = devicePublic ||
- lockscreenUserManager.isLockscreenPublicMode(notifUserId)
- val userPublic = when {
- // if we're not on the lockscreen, we're definitely private
- !userLockscreen -> false
- // we are on the lockscreen, so unless we're dynamically unlocked, we're
- // definitely public
- !dynamicallyUnlocked -> true
- // we're dynamically unlocked, but check if the notification needs
- // a separate challenge if it's from a work profile
- else -> when (notifUserId) {
- currentUserId -> false
- UserHandle.USER_ALL -> false
- else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId)
+ val userLockscreen =
+ devicePublic || lockscreenUserManager.isLockscreenPublicMode(notifUserId)
+ val userPublic =
+ when {
+ // if we're not on the lockscreen, we're definitely private
+ !userLockscreen -> false
+ // we are on the lockscreen, so unless we're dynamically unlocked, we're
+ // definitely public
+ !dynamicallyUnlocked -> true
+ // we're dynamically unlocked, but check if the notification needs
+ // a separate challenge if it's from a work profile
+ else ->
+ when (notifUserId) {
+ currentUserId -> false
+ UserHandle.USER_ALL -> false
+ else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId)
+ }
}
- }
- val shouldProtectNotification = screenshareNotificationHiding() &&
- sensitiveNotificationProtectionController.shouldProtectNotification(entry)
+ val shouldProtectNotification =
+ screenshareNotificationHiding() &&
+ sensitiveNotificationProtectionController.shouldProtectNotification(entry)
val needsRedaction = lockscreenUserManager.needsRedaction(entry)
val isSensitive = userPublic && needsRedaction
@@ -149,9 +204,7 @@
}
}
-private fun extractAllRepresentativeEntries(
- entries: List<ListEntry>
-): Sequence<NotificationEntry> =
+private fun extractAllRepresentativeEntries(entries: List<ListEntry>): Sequence<NotificationEntry> =
entries.asSequence().flatMap(::extractAllRepresentativeEntries)
private fun extractAllRepresentativeEntries(listEntry: ListEntry): Sequence<NotificationEntry> =
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/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index f693409..21ec14f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -289,6 +289,7 @@
labelRes = R.string.quick_settings_work_mode_label,
),
instanceId = uiEventLogger.getNewInstanceId(),
+ autoRemoveOnUnavailable = false,
)
/** Inject work mode into tileViewModelMap in QSModule */
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 94ff65e..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
@@ -26,6 +26,7 @@
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -49,6 +50,7 @@
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
@@ -61,6 +63,9 @@
import com.airbnb.lottie.compose.rememberLottieDynamicProperty
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS
import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGesture.BACK
import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureHandler
@@ -78,23 +83,49 @@
) {
val screenColors = rememberScreenColors()
BackHandler(onBack = onBack)
- var gestureDone by remember { mutableStateOf(false) }
+ var gestureState by remember { mutableStateOf(GestureState.NOT_STARTED) }
val swipeDistanceThresholdPx =
LocalContext.current.resources.getDimensionPixelSize(
com.android.internal.R.dimen.system_gestures_distance_threshold
)
val gestureHandler =
remember(swipeDistanceThresholdPx) {
- TouchpadGestureHandler(BACK, swipeDistanceThresholdPx, onDone = { gestureDone = true })
+ TouchpadGestureHandler(
+ BACK,
+ swipeDistanceThresholdPx,
+ onGestureStateChanged = { gestureState = it }
+ )
}
+ TouchpadGesturesHandlingBox(gestureHandler, gestureState) {
+ GestureTutorialContent(gestureState, onDoneButtonClicked, screenColors)
+ }
+}
+
+@Composable
+private fun TouchpadGesturesHandlingBox(
+ gestureHandler: TouchpadGestureHandler,
+ gestureState: GestureState,
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.() -> Unit
+) {
Box(
modifier =
- Modifier.fillMaxSize()
+ modifier
+ .fillMaxSize()
// we need to use pointerInteropFilter because some info about touchpad gestures is
// only available in MotionEvent
- .pointerInteropFilter(onTouchEvent = gestureHandler::onMotionEvent)
+ .pointerInteropFilter(
+ onTouchEvent = { event ->
+ // FINISHED is the final state so we don't need to process touches anymore
+ if (gestureState != FINISHED) {
+ gestureHandler.onMotionEvent(event)
+ } else {
+ false
+ }
+ }
+ )
) {
- GestureTutorialContent(gestureDone, onDoneButtonClicked, screenColors)
+ content()
}
}
@@ -126,14 +157,14 @@
@Composable
private fun GestureTutorialContent(
- gestureDone: Boolean,
+ gestureState: GestureState,
onDoneButtonClicked: () -> Unit,
screenColors: TutorialScreenColors
) {
val animatedColor by
animateColorAsState(
targetValue =
- if (gestureDone) screenColors.successBackgroundColor
+ if (gestureState == FINISHED) screenColors.successBackgroundColor
else screenColors.backgroundColor,
animationSpec = tween(durationMillis = 150, easing = LinearEasing),
label = "backgroundColor"
@@ -148,15 +179,17 @@
Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
TutorialDescription(
titleTextId =
- if (gestureDone) R.string.touchpad_tutorial_gesture_done
+ 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))
TutorialAnimation(
- gestureDone,
+ gestureState,
screenColors.animationProperties,
modifier = Modifier.weight(1f).padding(top = 8.dp)
)
@@ -189,27 +222,38 @@
@Composable
fun TutorialAnimation(
- gestureDone: Boolean,
+ gestureState: GestureState,
animationProperties: LottieDynamicProperties,
modifier: Modifier = Modifier
) {
Column(modifier = modifier.fillMaxWidth()) {
- val resId = if (gestureDone) R.raw.trackpad_back_success else R.raw.trackpad_back_edu
+ val resId =
+ if (gestureState == FINISHED) R.raw.trackpad_back_success else R.raw.trackpad_back_edu
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(resId))
- val progress by
- animateLottieCompositionAsState(
- composition,
- iterations = if (gestureDone) 1 else LottieConstants.IterateForever
- )
+ val progress = progressForGestureState(composition, gestureState)
LottieAnimation(
composition = composition,
- progress = { progress },
+ progress = progress,
dynamicProperties = animationProperties
)
}
}
@Composable
+private fun progressForGestureState(
+ composition: LottieComposition?,
+ gestureState: GestureState
+): () -> Float {
+ if (gestureState == IN_PROGRESS) {
+ return { 0f } // when gesture is in progress, animation should freeze on 1st frame
+ } else {
+ val iterations = if (gestureState == FINISHED) 1 else LottieConstants.IterateForever
+ val animationState by animateLottieCompositionAsState(composition, iterations = iterations)
+ return { animationState }
+ }
+}
+
+@Composable
fun rememberColorFilterProperty(
layerName: String,
color: Color
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
index 1fa7a0c..6fa9bcd 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
@@ -17,23 +17,26 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
import android.view.MotionEvent
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NOT_STARTED
import kotlin.math.abs
/**
- * Monitor for touchpad gestures that calls [gestureDoneCallback] when gesture was successfully
- * done. All tracked motion events should be passed to [processTouchpadEvent]
+ * Monitor for touchpad gestures that calls [gestureStateChangedCallback] when [GestureState]
+ * changes. All tracked motion events should be passed to [processTouchpadEvent]
*/
interface TouchpadGestureMonitor {
val gestureDistanceThresholdPx: Int
- val gestureDoneCallback: () -> Unit
+ val gestureStateChangedCallback: (GestureState) -> Unit
fun processTouchpadEvent(event: MotionEvent)
}
class BackGestureMonitor(
override val gestureDistanceThresholdPx: Int,
- override val gestureDoneCallback: () -> Unit
+ override val gestureStateChangedCallback: (GestureState) -> Unit
) : TouchpadGestureMonitor {
private var xStart = 0f
@@ -44,13 +47,16 @@
MotionEvent.ACTION_DOWN -> {
if (isThreeFingerTouchpadSwipe(event)) {
xStart = event.x
+ gestureStateChangedCallback(IN_PROGRESS)
}
}
MotionEvent.ACTION_UP -> {
if (isThreeFingerTouchpadSwipe(event)) {
val distance = abs(event.x - xStart)
if (distance >= gestureDistanceThresholdPx) {
- gestureDoneCallback()
+ gestureStateChangedCallback(FINISHED)
+ } else {
+ gestureStateChangedCallback(NOT_STARTED)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureState.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureState.kt
new file mode 100644
index 0000000..446875a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureState.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.tutorial.ui.gesture
+
+enum class GestureState {
+ NOT_STARTED,
+ IN_PROGRESS,
+ FINISHED
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGesture.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGesture.kt
index 4ae9c7b..190da62 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGesture.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGesture.kt
@@ -22,10 +22,10 @@
fun toMonitor(
swipeDistanceThresholdPx: Int,
- gestureDoneCallback: () -> Unit
+ onStateChanged: (GestureState) -> Unit
): TouchpadGestureMonitor {
return when (this) {
- BACK -> BackGestureMonitor(swipeDistanceThresholdPx, gestureDoneCallback)
+ BACK -> BackGestureMonitor(swipeDistanceThresholdPx, onStateChanged)
else -> throw IllegalArgumentException("Not implemented yet")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
index dc8471c..cac2a99 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
@@ -26,11 +26,11 @@
class TouchpadGestureHandler(
touchpadGesture: TouchpadGesture,
swipeDistanceThresholdPx: Int,
- onDone: () -> Unit
+ onGestureStateChanged: (GestureState) -> Unit
) {
private val gestureRecognition =
- touchpadGesture.toMonitor(swipeDistanceThresholdPx, gestureDoneCallback = onDone)
+ touchpadGesture.toMonitor(swipeDistanceThresholdPx, onStateChanged = onGestureStateChanged)
fun onMotionEvent(event: MotionEvent): Boolean {
// events from touchpad have SOURCE_MOUSE and not SOURCE_TOUCHPAD because of legacy reasons
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index efaca7a..5d8b6f1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -19,8 +19,8 @@
import android.content.ContentResolver
import android.content.Context
import android.media.AudioManager
-import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.flags.Flags
import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
@@ -80,7 +80,7 @@
@Application coroutineScope: CoroutineScope,
@Background coroutineContext: CoroutineContext,
): AudioSharingRepository =
- if (BluetoothUtils.isAudioSharingEnabled() && localBluetoothManager != null) {
+ if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
AudioSharingRepositoryImpl(
contentResolver,
localBluetoothManager,
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/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 0505727..689fc7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -28,7 +28,11 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.StatusBarState
@@ -45,6 +49,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
+import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -52,6 +57,7 @@
import com.android.systemui.util.mockito.withArgCaptor
import dagger.BindsInstance
import dagger.Component
+import kotlinx.coroutines.CoroutineScope
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -64,6 +70,8 @@
@RunWith(AndroidJUnit4::class)
class SensitiveContentCoordinatorTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+
val dynamicPrivacyController: DynamicPrivacyController = mock()
val lockscreenUserManager: NotificationLockscreenUserManager = mock()
val pipeline: NotifPipeline = mock()
@@ -73,6 +81,8 @@
val mSelectedUserInteractor: SelectedUserInteractor = mock()
val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController =
mock()
+ val deviceEntryInteractor: DeviceEntryInteractor = mock()
+ val sceneInteractor: SceneInteractor = mock()
val coordinator: SensitiveContentCoordinator =
DaggerTestSensitiveContentCoordinatorComponent.factory()
@@ -83,7 +93,10 @@
statusBarStateController,
keyguardStateController,
mSelectedUserInteractor,
- sensitiveNotificationProtectionController
+ sensitiveNotificationProtectionController,
+ deviceEntryInteractor,
+ sceneInteractor,
+ kosmos.applicationCoroutineScope,
)
.coordinator
@@ -136,8 +149,7 @@
@Test
@EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
fun screenshareSecretFilter_sensitiveInctive_noFiltersSecret() {
- whenever(sensitiveNotificationProtectionController.isSensitiveStateActive)
- .thenReturn(false)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(false)
coordinator.attach(pipeline)
val filter = withArgCaptor<NotifFilter> { verify(pipeline).addFinalizeFilter(capture()) }
@@ -683,10 +695,11 @@
val mockSbn: StatusBarNotification =
mock<StatusBarNotification>().apply { whenever(user).thenReturn(mockUserHandle) }
val mockRow: ExpandableNotificationRow = mock<ExpandableNotificationRow>()
- val mockEntry = mock<NotificationEntry>().apply {
- whenever(sbn).thenReturn(mockSbn)
- whenever(row).thenReturn(mockRow)
- }
+ val mockEntry =
+ mock<NotificationEntry>().apply {
+ whenever(sbn).thenReturn(mockSbn)
+ whenever(row).thenReturn(mockRow)
+ }
whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction)
whenever(mockEntry.rowExists()).thenReturn(true)
return object : ListEntry("key", 0) {
@@ -737,6 +750,9 @@
@BindsInstance selectedUserInteractor: SelectedUserInteractor,
@BindsInstance
sensitiveNotificationProtectionController: SensitiveNotificationProtectionController,
+ @BindsInstance deviceEntryInteractor: DeviceEntryInteractor,
+ @BindsInstance sceneInteractor: SceneInteractor,
+ @BindsInstance @Application scope: CoroutineScope,
): TestSensitiveContentCoordinatorComponent
}
}
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/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
index cf0db7b..8875b84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
@@ -28,6 +28,9 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NOT_STARTED
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -36,16 +39,19 @@
@RunWith(AndroidJUnit4::class)
class BackGestureMonitorTest : SysuiTestCase() {
- private var gestureDoneWasCalled = false
- private val gestureDoneCallback = { gestureDoneWasCalled = true }
- private val gestureMonitor = BackGestureMonitor(SWIPE_DISTANCE.toInt(), gestureDoneCallback)
+ private var gestureState = NOT_STARTED
+ private val gestureMonitor =
+ BackGestureMonitor(
+ gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
+ gestureStateChangedCallback = { gestureState = it }
+ )
companion object {
const val SWIPE_DISTANCE = 100f
}
@Test
- fun triggersGestureDoneForThreeFingerGestureRight() {
+ fun triggersGestureFinishedForThreeFingerGestureRight() {
val events =
listOf(
threeFingerEvent(ACTION_DOWN, x = 0f, y = 0f),
@@ -59,11 +65,11 @@
events.forEach { gestureMonitor.processTouchpadEvent(it) }
- assertThat(gestureDoneWasCalled).isTrue()
+ assertThat(gestureState).isEqualTo(FINISHED)
}
@Test
- fun triggersGestureDoneForThreeFingerGestureLeft() {
+ fun triggersGestureFinishedForThreeFingerGestureLeft() {
val events =
listOf(
threeFingerEvent(ACTION_DOWN, x = SWIPE_DISTANCE, y = 0f),
@@ -77,7 +83,21 @@
events.forEach { gestureMonitor.processTouchpadEvent(it) }
- assertThat(gestureDoneWasCalled).isTrue()
+ assertThat(gestureState).isEqualTo(FINISHED)
+ }
+
+ @Test
+ fun triggersGestureProgressForThreeFingerGestureStarted() {
+ val events =
+ listOf(
+ threeFingerEvent(ACTION_DOWN, x = SWIPE_DISTANCE, y = 0f),
+ threeFingerEvent(ACTION_POINTER_DOWN, x = SWIPE_DISTANCE, y = 0f),
+ threeFingerEvent(ACTION_POINTER_DOWN, x = SWIPE_DISTANCE, y = 0f),
+ )
+
+ events.forEach { gestureMonitor.processTouchpadEvent(it) }
+
+ assertThat(gestureState).isEqualTo(IN_PROGRESS)
}
private fun threeFingerEvent(action: Int, x: Float, y: Float): MotionEvent {
@@ -91,7 +111,7 @@
}
@Test
- fun doesntTriggerGestureDone_onThreeFingersSwipeUp() {
+ fun doesntTriggerGestureFinished_onThreeFingersSwipeUp() {
val events =
listOf(
threeFingerEvent(ACTION_DOWN, x = 0f, y = 0f),
@@ -105,11 +125,11 @@
events.forEach { gestureMonitor.processTouchpadEvent(it) }
- assertThat(gestureDoneWasCalled).isFalse()
+ assertThat(gestureState).isEqualTo(NOT_STARTED)
}
@Test
- fun doesntTriggerGestureDone_onTwoFingersSwipe() {
+ fun doesntTriggerGestureFinished_onTwoFingersSwipe() {
fun twoFingerEvent(action: Int, x: Float, y: Float) =
motionEvent(
action = action,
@@ -127,11 +147,11 @@
events.forEach { gestureMonitor.processTouchpadEvent(it) }
- assertThat(gestureDoneWasCalled).isFalse()
+ assertThat(gestureState).isEqualTo(NOT_STARTED)
}
@Test
- fun doesntTriggerGestureDone_onFourFingersSwipe() {
+ fun doesntTriggerGestureFinished_onFourFingersSwipe() {
fun fourFingerEvent(action: Int, x: Float, y: Float) =
motionEvent(
action = action,
@@ -155,6 +175,6 @@
events.forEach { gestureMonitor.processTouchpadEvent(it) }
- assertThat(gestureDoneWasCalled).isFalse()
+ assertThat(gestureState).isEqualTo(NOT_STARTED)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
index 769f264..dc4d5f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
@@ -32,6 +32,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NOT_STARTED
import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGesture.BACK
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -41,8 +43,8 @@
@RunWith(AndroidJUnit4::class)
class TouchpadGestureHandlerTest : SysuiTestCase() {
- private var gestureDone = false
- private val handler = TouchpadGestureHandler(BACK, SWIPE_DISTANCE) { gestureDone = true }
+ private var gestureState = NOT_STARTED
+ private val handler = TouchpadGestureHandler(BACK, SWIPE_DISTANCE) { gestureState = it }
companion object {
const val SWIPE_DISTANCE = 100
@@ -84,7 +86,7 @@
fun triggersGestureDoneForThreeFingerGesture() {
backGestureEvents().forEach { handler.onMotionEvent(it) }
- assertThat(gestureDone).isTrue()
+ assertThat(gestureState).isEqualTo(FINISHED)
}
private fun backGestureEvents(): List<MotionEvent> {
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/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index 2ca338a..f3d5b7d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -33,7 +33,7 @@
private var _userHandle: UserHandle = UserHandle.of(_userId),
private var _userInfo: UserInfo = mock(),
private var _userProfiles: List<UserInfo> = emptyList(),
- userContentResolver: ContentResolver = MockContentResolver(),
+ userContentResolverProvider: () -> ContentResolver = { MockContentResolver() },
userContext: Context = mock(),
private val onCreateCurrentUserContext: (Context) -> Context = { mock() },
) : UserTracker {
@@ -41,14 +41,19 @@
override val userId: Int
get() = _userId
+
override val userHandle: UserHandle
get() = _userHandle
+
override val userInfo: UserInfo
get() = _userInfo
+
override val userProfiles: List<UserInfo>
get() = _userProfiles
- override val userContentResolver: ContentResolver = userContentResolver
+ // userContentResolver is lazy because Ravenwood doesn't support MockContentResolver()
+ // and we still want to allow people use this class for tests that don't use it.
+ override val userContentResolver: ContentResolver by lazy { userContentResolverProvider() }
override val userContext: Context = userContext
override fun addCallback(callback: UserTracker.Callback, executor: Executor) {
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/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 7f43fae..3123268 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -174,6 +174,7 @@
"haptics",
"hardware_backed_security_mainline",
"input",
+ "incremental",
"llvm_and_toolchains",
"lse_desktop_experience",
"machine_learning",
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/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/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 1a8e44b..1fdb57c 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -950,7 +950,7 @@
|| isPackageOrComponentAllowed(component.getPackageName(), userId))) {
return false;
}
- return componentHasBindPermission(component, userId);
+ return isValidService(component, userId);
}
private boolean componentHasBindPermission(ComponentName component, int userId) {
@@ -1302,11 +1302,12 @@
if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) {
final ComponentName component = ComponentName.unflattenFromString(
approvedPackageOrComponent);
- if (component != null && !componentHasBindPermission(component, userId)) {
+ if (component != null && !isValidService(component, userId)) {
approved.removeAt(j);
if (DEBUG) {
Slog.v(TAG, "Removing " + approvedPackageOrComponent
- + " from approved list; no bind permission found "
+ + " from approved list; no bind permission or "
+ + "service interface filter found "
+ mConfig.bindPermission);
}
}
@@ -1325,6 +1326,11 @@
}
}
+ protected boolean isValidService(ComponentName component, int userId) {
+ return componentHasBindPermission(component, userId) && queryPackageForServices(
+ component.getPackageName(), userId).contains(component);
+ }
+
protected boolean isValidEntry(String packageOrComponent, int userId) {
return hasMatchingServices(packageOrComponent, userId);
}
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/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
index f9bad59..46bd7af 100644
--- a/services/core/java/com/android/server/vibrator/HalVibration.java
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -108,23 +108,6 @@
}
/**
- * Resolves the default vibration amplitude of {@link #getEffectToPlay()} and each fallback.
- *
- * @param defaultAmplitude An integer in [1,255] representing the device default amplitude to
- * replace the {@link VibrationEffect#DEFAULT_AMPLITUDE}.
- */
- public void resolveEffects(int defaultAmplitude) {
- CombinedVibration newEffect =
- mEffectToPlay.transform(VibrationEffect::resolve, defaultAmplitude);
- if (!Objects.equals(mEffectToPlay, newEffect)) {
- mEffectToPlay = newEffect;
- }
- for (int i = 0; i < mFallbacks.size(); i++) {
- mFallbacks.setValueAt(i, mFallbacks.valueAt(i).resolve(defaultAmplitude));
- }
- }
-
- /**
* Scales the {@link #getEffectToPlay()} and each fallback effect based on the vibration usage.
*/
public void scaleEffects(VibrationScaler scaler) {
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 98a2ba0d..3f9da82 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -46,6 +46,8 @@
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
private static final VibrationAttributes COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
+ private static final VibrationAttributes IME_FEEDBACK_VIBRATION_ATTRIBUTES =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_IME_FEEDBACK);
private final VibratorInfo mVibratorInfo;
private final boolean mHapticTextHandleEnabled;
@@ -219,8 +221,6 @@
}
int vibFlags = 0;
- boolean fromIme =
- (privFlags & HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS) != 0;
boolean bypassVibrationIntensitySetting =
(flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0;
if (bypassVibrationIntensitySetting) {
@@ -229,9 +229,6 @@
if (shouldBypassInterruptionPolicy(effectId)) {
vibFlags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
}
- if (shouldBypassIntensityScale(effectId, fromIme)) {
- vibFlags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
- }
return vibFlags == 0 ? attrs : new VibrationAttributes.Builder(attrs)
.setFlags(vibFlags).build();
@@ -362,22 +359,6 @@
/* fallbackForPredefinedEffect= */ predefinedEffectFallback);
}
- private boolean shouldBypassIntensityScale(int effectId, boolean isIme) {
- if (!Flags.keyboardCategoryEnabled() || mKeyboardVibrationFixedAmplitude < 0 || !isIme) {
- // Shouldn't bypass if not support keyboard category, no fixed amplitude or not an IME.
- return false;
- }
- switch (effectId) {
- case HapticFeedbackConstants.KEYBOARD_TAP:
- return mVibratorInfo.isPrimitiveSupported(
- VibrationEffect.Composition.PRIMITIVE_CLICK);
- case HapticFeedbackConstants.KEYBOARD_RELEASE:
- return mVibratorInfo.isPrimitiveSupported(
- VibrationEffect.Composition.PRIMITIVE_TICK);
- }
- return false;
- }
-
private VibrationAttributes createKeyboardVibrationAttributes(
@HapticFeedbackConstants.PrivateFlags int privFlags) {
// Use touch attribute when the keyboard category is disable.
@@ -388,7 +369,8 @@
if ((privFlags & HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS) == 0) {
return TOUCH_VIBRATION_ATTRIBUTES;
}
- return new VibrationAttributes.Builder(TOUCH_VIBRATION_ATTRIBUTES)
+ return new VibrationAttributes.Builder(IME_FEEDBACK_VIBRATION_ATTRIBUTES)
+ // TODO(b/332661766): Remove CATEGORY_KEYBOARD logic
.setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
.build();
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 0206155..fb92d60 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -21,6 +21,7 @@
import static android.os.VibrationAttributes.USAGE_ALARM;
import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_IME_FEEDBACK;
import static android.os.VibrationAttributes.USAGE_MEDIA;
import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
@@ -560,6 +561,7 @@
mKeyboardVibrationOn = loadSystemSetting(
Settings.System.KEYBOARD_VIBRATION_ENABLED, 1, userHandle) > 0;
+ int keyboardIntensity = getDefaultIntensity(USAGE_IME_FEEDBACK);
int alarmIntensity = toIntensity(
loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1, userHandle),
getDefaultIntensity(USAGE_ALARM));
@@ -610,6 +612,12 @@
mCurrentVibrationIntensities.put(USAGE_TOUCH, hapticFeedbackIntensity);
}
+ if (mVibrationConfig.isKeyboardVibrationSettingsSupported()) {
+ mCurrentVibrationIntensities.put(USAGE_IME_FEEDBACK, keyboardIntensity);
+ } else {
+ mCurrentVibrationIntensities.put(USAGE_IME_FEEDBACK, hapticFeedbackIntensity);
+ }
+
// A11y is not disabled by any haptic feedback setting.
mCurrentVibrationIntensities.put(USAGE_ACCESSIBILITY, positiveHapticFeedbackIntensity);
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 8c9a92d..7152844 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -21,7 +21,6 @@
import android.os.Build;
import android.os.CombinedVibration;
import android.os.IBinder;
-import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
@@ -177,16 +176,11 @@
expectIsVibrationThread(true);
}
- if (!mVibration.callerInfo.attrs.isFlagSet(
- VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
- if (Flags.adaptiveHapticsEnabled()) {
- waitForVibrationParamsIfRequired();
- }
- // Scale resolves the default amplitudes from the effect before scaling them.
- mVibration.scaleEffects(mVibrationScaler);
- } else {
- mVibration.resolveEffects(mVibrationScaler.getDefaultVibrationAmplitude());
+ if (Flags.adaptiveHapticsEnabled()) {
+ waitForVibrationParamsIfRequired();
}
+ // Scale resolves the default amplitudes from the effect before scaling them.
+ mVibration.scaleEffects(mVibrationScaler);
mVibration.adaptToDevice(mDeviceAdapter);
CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffectToPlay());
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 48c4a68..7610d7d 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -103,8 +103,7 @@
new VibrationAttributes.Builder().build();
private static final int ATTRIBUTES_ALL_BYPASS_FLAGS =
VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
- | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF
- | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
+ | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
/** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
@@ -925,8 +924,7 @@
private VibrationStepConductor createVibrationStepConductor(HalVibration vib) {
CompletableFuture<Void> requestVibrationParamsFuture = null;
- if (Flags.adaptiveHapticsEnabled() && !vib.callerInfo.attrs.isFlagSet(
- VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)
+ if (Flags.adaptiveHapticsEnabled()
&& mVibratorControlService.shouldRequestVibrationParams(
vib.callerInfo.attrs.getUsage())) {
requestVibrationParamsFuture =
@@ -940,13 +938,8 @@
}
private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
- if (!vib.callerInfo.attrs.isFlagSet(
- VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
- // Scale resolves the default amplitudes from the effect before scaling them.
- vib.scaleEffects(mVibrationScaler);
- } else {
- vib.resolveEffects(mVibrationScaler.getDefaultVibrationAmplitude());
- }
+ // Scale resolves the default amplitudes from the effect before scaling them.
+ vib.scaleEffects(mVibrationScaler);
mInputDeviceDelegate.vibrateIfAvailable(vib.callerInfo, vib.getEffectToPlay());
return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
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/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 3b99954..1994174 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -52,7 +52,7 @@
* display change transition. In this case, we will queue all display updates until the current
* transition's collection finishes and then apply them afterwards.
*/
-public class DeferredDisplayUpdater implements DisplayUpdater {
+class DeferredDisplayUpdater {
/**
* List of fields that could be deferred before applying to DisplayContent.
@@ -110,7 +110,7 @@
continueScreenUnblocking();
};
- public DeferredDisplayUpdater(@NonNull DisplayContent displayContent) {
+ DeferredDisplayUpdater(@NonNull DisplayContent displayContent) {
mDisplayContent = displayContent;
mNonOverrideDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
}
@@ -122,8 +122,7 @@
*
* @param finishCallback is called when all pending display updates are finished
*/
- @Override
- public void updateDisplayInfo(@NonNull Runnable finishCallback) {
+ void updateDisplayInfo(@NonNull Runnable finishCallback) {
// Get the latest display parameters from the DisplayManager
final DisplayInfo displayInfo = getCurrentDisplayInfo();
@@ -310,9 +309,11 @@
return !Objects.equals(first.uniqueId, second.uniqueId);
}
- @Override
- public void onDisplayContentDisplayPropertiesPostChanged(int previousRotation, int newRotation,
- DisplayAreaInfo newDisplayAreaInfo) {
+ /**
+ * Called after physical display has changed and after DisplayContent applied new display
+ * properties.
+ */
+ void onDisplayContentDisplayPropertiesPostChanged() {
// Unblock immediately in case there is no transition. This is unlikely to happen.
if (mScreenUnblocker != null && !mDisplayContent.mTransitionController.inTransition()) {
mScreenUnblocker.sendToTarget();
@@ -320,13 +321,16 @@
}
}
- @Override
- public void onDisplaySwitching(boolean switching) {
+ /**
+ * Called with {@code true} when physical display is going to switch. And {@code false} when
+ * the display is turned on or the device goes to sleep.
+ */
+ void onDisplaySwitching(boolean switching) {
mShouldWaitForTransitionWhenScreenOn = switching;
}
- @Override
- public boolean waitForTransition(@NonNull Message screenUnblocker) {
+ /** Returns {@code true} if the transition will control when to turn on the screen. */
+ boolean waitForTransition(@NonNull Message screenUnblocker) {
if (!Flags.waitForTransitionOnDisplaySwitch()) return false;
if (!mShouldWaitForTransitionWhenScreenOn) {
return false;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 403c307..b31ae90 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -159,7 +159,6 @@
import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
-import static com.android.window.flags.Flags.deferDisplayUpdates;
import static com.android.window.flags.Flags.explicitRefreshRateHints;
import android.annotation.IntDef;
@@ -478,7 +477,7 @@
AppCompatCameraPolicy mAppCompatCameraPolicy;
DisplayFrames mDisplayFrames;
- final DisplayUpdater mDisplayUpdater;
+ final DeferredDisplayUpdater mDisplayUpdater;
private boolean mInTouchMode;
@@ -623,7 +622,6 @@
@VisibleForTesting
final DeviceStateController mDeviceStateController;
final Consumer<DeviceStateController.DeviceState> mDeviceStateConsumer;
- final PhysicalDisplaySwitchTransitionLauncher mDisplaySwitchTransitionLauncher;
final RemoteDisplayChangeController mRemoteDisplayChangeController;
/** Windows added since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
@@ -1140,11 +1138,7 @@
mWallpaperController.resetLargestDisplay(display);
display.getDisplayInfo(mDisplayInfo);
display.getMetrics(mDisplayMetrics);
- if (deferDisplayUpdates()) {
- mDisplayUpdater = new DeferredDisplayUpdater(this);
- } else {
- mDisplayUpdater = new ImmediateDisplayUpdater(this);
- }
+ mDisplayUpdater = new DeferredDisplayUpdater(this);
mSystemGestureExclusionLimit = mWmService.mConstants.mSystemGestureExclusionLimitDp
* mDisplayMetrics.densityDpi / DENSITY_DEFAULT;
isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
@@ -1168,8 +1162,6 @@
mAppTransitionController = new AppTransitionController(mWmService, this);
mTransitionController.registerLegacyListener(mFixedRotationTransitionListener);
mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
- mDisplaySwitchTransitionLauncher = new PhysicalDisplaySwitchTransitionLauncher(this,
- mTransitionController);
mRemoteDisplayChangeController = new RemoteDisplayChangeController(this);
final InputChannel inputChannel = mWmService.mInputManager.monitorInput(
@@ -1190,7 +1182,6 @@
mDeviceStateConsumer =
(@NonNull DeviceStateController.DeviceState newFoldState) -> {
- mDisplaySwitchTransitionLauncher.foldStateChanged(newFoldState);
mDisplayRotation.foldStateChanged(newFoldState);
};
mDeviceStateController.registerDeviceStateCallback(mDeviceStateConsumer,
@@ -3094,8 +3085,6 @@
// metrics are updated as rotation settings might depend on them
mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this,
/* includeRotationSettings */ false);
- mDisplayUpdater.onDisplayContentDisplayPropertiesPreChanged(mDisplayId,
- mInitialDisplayWidth, mInitialDisplayHeight, newWidth, newHeight);
mDisplayRotation.physicalDisplayChanged();
mDisplayPolicy.physicalDisplayChanged();
}
@@ -3130,8 +3119,7 @@
if (physicalDisplayChanged) {
mDisplayPolicy.physicalDisplayUpdated();
- mDisplayUpdater.onDisplayContentDisplayPropertiesPostChanged(currentRotation,
- getRotation(), getDisplayAreaInfo());
+ mDisplayUpdater.onDisplayContentDisplayPropertiesPostChanged();
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayUpdater.java b/services/core/java/com/android/server/wm/DisplayUpdater.java
deleted file mode 100644
index 918b180..0000000
--- a/services/core/java/com/android/server/wm/DisplayUpdater.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.annotation.NonNull;
-import android.os.Message;
-import android.view.Surface;
-import android.window.DisplayAreaInfo;
-
-/**
- * Interface for a helper class that manages updates of DisplayInfo coming from DisplayManager
- */
-interface DisplayUpdater {
- /**
- * Reads the latest display parameters from the display manager and returns them in a callback.
- * If there are pending display updates, it will wait for them to finish first and only then it
- * will call the callback with the latest display parameters.
- *
- * @param callback is called when all pending display updates are finished
- */
- void updateDisplayInfo(@NonNull Runnable callback);
-
- /**
- * Called when physical display has changed and before DisplayContent has applied new display
- * properties
- */
- default void onDisplayContentDisplayPropertiesPreChanged(int displayId, int initialDisplayWidth,
- int initialDisplayHeight, int newWidth, int newHeight) {
- }
-
- /**
- * Called after physical display has changed and after DisplayContent applied new display
- * properties
- */
- default void onDisplayContentDisplayPropertiesPostChanged(
- @Surface.Rotation int previousRotation, @Surface.Rotation int newRotation,
- @NonNull DisplayAreaInfo newDisplayAreaInfo) {
- }
-
- /**
- * Called with {@code true} when physical display is going to switch. And {@code false} when
- * the display is turned on or the device goes to sleep.
- */
- default void onDisplaySwitching(boolean switching) {
- }
-
- /** Returns {@code true} if the transition will control when to turn on the screen. */
- default boolean waitForTransition(@NonNull Message screenUnBlocker) {
- return false;
- }
-}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index e0d69b0..4ec318b 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -16,9 +16,12 @@
package com.android.server.wm;
+import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
+import android.companion.virtualdevice.flags.Flags;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -26,6 +29,7 @@
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
+import android.view.Display;
import android.window.DisplayWindowPolicyController;
import java.io.PrintWriter;
@@ -80,6 +84,9 @@
if (hasDisplayCategory(activities.get(i))) {
return false;
}
+ if (!launchAllowedByDisplayPolicy(activities.get(i))) {
+ return false;
+ }
}
return true;
}
@@ -95,7 +102,13 @@
if (mDisplayWindowPolicyController == null) {
// Missing controller means that this display has no categories for activity launch
// restriction.
- return !hasDisplayCategory(activityInfo);
+ if (hasDisplayCategory(activityInfo)) {
+ return false;
+ }
+ if (!launchAllowedByDisplayPolicy(activityInfo)) {
+ return false;
+ }
+ return true;
}
return mDisplayWindowPolicyController.canActivityBeLaunched(activityInfo, intent,
windowingMode, launchingFromDisplayId, isNewTask);
@@ -112,6 +125,24 @@
return false;
}
+ private boolean launchAllowedByDisplayPolicy(ActivityInfo aInfo) {
+ if (!Flags.enforceRemoteDeviceOptOutOnAllVirtualDisplays()) {
+ return true;
+ }
+ int displayType = mDisplayContent.getDisplay().getType();
+ if (displayType != Display.TYPE_VIRTUAL && displayType != Display.TYPE_WIFI) {
+ return true;
+ }
+ if ((aInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+ Slog.d(TAG,
+ String.format("Checking activity launch on display %d, activity requires"
+ + " android:canDisplayOnRemoteDevices=true",
+ mDisplayContent.mDisplayId));
+ return false;
+ }
+ return true;
+ }
+
/**
* @see DisplayWindowPolicyController#keepActivityOnWindowFlagsChanged(ActivityInfo, int, int)
*/
diff --git a/services/core/java/com/android/server/wm/ImmediateDisplayUpdater.java b/services/core/java/com/android/server/wm/ImmediateDisplayUpdater.java
deleted file mode 100644
index 4af9013..0000000
--- a/services/core/java/com/android/server/wm/ImmediateDisplayUpdater.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.annotation.NonNull;
-import android.view.DisplayInfo;
-import android.window.DisplayAreaInfo;
-
-/**
- * DisplayUpdater that immediately applies new DisplayInfo properties
- */
-public class ImmediateDisplayUpdater implements DisplayUpdater {
-
- private final DisplayContent mDisplayContent;
- private final DisplayInfo mDisplayInfo = new DisplayInfo();
-
- public ImmediateDisplayUpdater(@NonNull DisplayContent displayContent) {
- mDisplayContent = displayContent;
- mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
- }
-
- @Override
- public void updateDisplayInfo(Runnable callback) {
- mDisplayContent.mWmService.mDisplayManagerInternal.getNonOverrideDisplayInfo(
- mDisplayContent.mDisplayId, mDisplayInfo);
- mDisplayContent.onDisplayInfoUpdated(mDisplayInfo);
- callback.run();
- }
-
- @Override
- public void onDisplayContentDisplayPropertiesPreChanged(int displayId, int initialDisplayWidth,
- int initialDisplayHeight, int newWidth, int newHeight) {
- mDisplayContent.mDisplaySwitchTransitionLauncher.requestDisplaySwitchTransitionIfNeeded(
- displayId, initialDisplayWidth, initialDisplayHeight, newWidth, newHeight);
- }
-
- @Override
- public void onDisplayContentDisplayPropertiesPostChanged(int previousRotation, int newRotation,
- DisplayAreaInfo newDisplayAreaInfo) {
- mDisplayContent.mDisplaySwitchTransitionLauncher.onDisplayUpdated(previousRotation,
- newRotation,
- newDisplayAreaInfo);
- }
-}
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/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
deleted file mode 100644
index 3cf301c..0000000
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH;
-
-import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
-import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
-import static com.android.server.wm.DeviceStateController.DeviceState.FOLDED;
-import static com.android.server.wm.DeviceStateController.DeviceState.HALF_FOLDED;
-import static com.android.server.wm.DeviceStateController.DeviceState.OPEN;
-
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Rect;
-import android.window.DisplayAreaInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerTransaction;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLogGroup;
-import com.android.internal.protolog.ProtoLog;
-import com.android.server.wm.DeviceStateController.DeviceState;
-
-public class PhysicalDisplaySwitchTransitionLauncher {
-
- private final DisplayContent mDisplayContent;
- private final ActivityTaskManagerService mAtmService;
- private final Context mContext;
- private final TransitionController mTransitionController;
-
- /**
- * If on a foldable device represents whether we need to show unfold animation when receiving
- * a physical display switch event
- */
- private boolean mShouldRequestTransitionOnDisplaySwitch = false;
- /**
- * Current device state from {@link android.hardware.devicestate.DeviceStateManager}
- */
- private DeviceState mDeviceState = DeviceState.UNKNOWN;
- private Transition mTransition;
-
- public PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent,
- TransitionController transitionController) {
- this(displayContent, displayContent.mWmService.mAtmService,
- displayContent.mWmService.mContext, transitionController);
- }
-
- @VisibleForTesting
- public PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent,
- ActivityTaskManagerService service, Context context,
- TransitionController transitionController) {
- mDisplayContent = displayContent;
- mAtmService = service;
- mContext = context;
- mTransitionController = transitionController;
- }
-
- /**
- * Called by the display manager just before it applied the device state, it is guaranteed
- * that in case of physical display change the
- * {@link PhysicalDisplaySwitchTransitionLauncher#requestDisplaySwitchTransitionIfNeeded}
- * method will be invoked *after* this one.
- */
- void foldStateChanged(DeviceState newDeviceState) {
- boolean isUnfolding = mDeviceState == FOLDED
- && (newDeviceState == HALF_FOLDED || newDeviceState == OPEN);
-
- if (isUnfolding) {
- // Request transition only if we are unfolding the device
- mShouldRequestTransitionOnDisplaySwitch = true;
- } else if (newDeviceState != HALF_FOLDED && newDeviceState != OPEN) {
- // Cancel the transition request in case if we are folding or switching to back
- // to the rear display before the displays got switched
- mShouldRequestTransitionOnDisplaySwitch = false;
- }
-
- mDeviceState = newDeviceState;
- }
-
- /**
- * Requests to start a transition for the physical display switch
- */
- public void requestDisplaySwitchTransitionIfNeeded(int displayId, int oldDisplayWidth,
- int oldDisplayHeight, int newDisplayWidth, int newDisplayHeight) {
- if (!mShouldRequestTransitionOnDisplaySwitch) return;
- if (!mTransitionController.isShellTransitionsEnabled()) return;
- if (!mDisplayContent.getLastHasContent()) return;
-
- boolean shouldRequestUnfoldTransition = mContext.getResources()
- .getBoolean(config_unfoldTransitionEnabled) && ValueAnimator.areAnimatorsEnabled();
-
- if (!shouldRequestUnfoldTransition) {
- return;
- }
-
- mTransition = null;
-
- if (mTransitionController.isCollecting()) {
- // Add display container to the currently collecting transition
- mTransition = mTransitionController.getCollectingTransition();
- mTransition.collect(mDisplayContent);
-
- // Make sure that transition is not ready until we finish the remote display change
- mTransition.setReady(mDisplayContent, false);
- mTransition.addFlag(TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH);
-
- ProtoLog.d(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- "Adding display switch to existing collecting transition");
- } else {
- final TransitionRequestInfo.DisplayChange displayChange =
- new TransitionRequestInfo.DisplayChange(displayId);
-
- final Rect startAbsBounds = new Rect(0, 0, oldDisplayWidth, oldDisplayHeight);
- displayChange.setStartAbsBounds(startAbsBounds);
- final Rect endAbsBounds = new Rect(0, 0, newDisplayWidth, newDisplayHeight);
- displayChange.setEndAbsBounds(endAbsBounds);
- displayChange.setPhysicalDisplayChanged(true);
-
- mTransition = mTransitionController.requestStartDisplayTransition(TRANSIT_CHANGE,
- 0 /* flags */, mDisplayContent, null /* remoteTransition */, displayChange);
- mTransition.collect(mDisplayContent);
- }
-
- if (mTransition != null) {
- mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
- }
-
- mShouldRequestTransitionOnDisplaySwitch = false;
- }
-
- /**
- * Called when physical display is getting updated, this could happen e.g. on foldable
- * devices when the physical underlying display is replaced.
- *
- * @param fromRotation rotation before the display change
- * @param toRotation rotation after the display change
- * @param newDisplayAreaInfo display area info after the display change
- */
- public void onDisplayUpdated(int fromRotation, int toRotation,
- @NonNull DisplayAreaInfo newDisplayAreaInfo) {
- if (mTransition == null) return;
-
- final boolean started = mDisplayContent.mRemoteDisplayChangeController
- .performRemoteDisplayChange(fromRotation, toRotation, newDisplayAreaInfo,
- this::continueDisplayUpdate);
-
- if (!started) {
- markTransitionAsReady();
- }
- }
-
- private void continueDisplayUpdate(@Nullable WindowContainerTransaction transaction) {
- if (mTransition == null) return;
-
- if (transaction != null) {
- mAtmService.mWindowOrganizerController.applyTransaction(transaction);
- }
-
- markTransitionAsReady();
- }
-
- private void markTransitionAsReady() {
- if (mTransition == null) return;
-
- mTransition.setAllReady();
- mTransition = null;
- }
-
-}
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 549c0ae..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;
@@ -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/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/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index e5c42082..fb82b872c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -888,7 +888,7 @@
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
service.addApprovedList("a", 0, true);
service.reregisterService(cn, 0);
@@ -919,7 +919,7 @@
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
service.addApprovedList("a", 0, false);
service.reregisterService(cn, 0);
@@ -950,7 +950,7 @@
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
service.addApprovedList("a/a", 0, true);
service.reregisterService(cn, 0);
@@ -981,7 +981,7 @@
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
service.addApprovedList("a/a", 0, false);
service.reregisterService(cn, 0);
@@ -1211,6 +1211,64 @@
}
@Test
+ public void testUpgradeAppNoIntentFilterNoRebind() throws Exception {
+ Context context = spy(getContext());
+ doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
+ mIpm, APPROVAL_BY_COMPONENT);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ addExpectedServices(service, packages, 0);
+
+ final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
+ final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
+
+ // Both components are approved initially
+ mExpectedPrimaryComponentNames.clear();
+ mExpectedPrimaryPackages.clear();
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+ mExpectedSecondaryComponentNames.clear();
+ mExpectedSecondaryPackages.clear();
+
+ loadXml(service);
+
+ //Component package/C1 loses serviceInterface intent filter
+ ManagedServices.Config config = service.getConfig();
+ when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).
+ thenAnswer(new Answer<List<ResolveInfo>>() {
+ @Override
+ public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
+ throws Throwable {
+ Object[] args = invocationOnMock.getArguments();
+ Intent invocationIntent = (Intent) args[0];
+ if (invocationIntent != null) {
+ if (invocationIntent.getAction().equals(config.serviceInterface)
+ && packages.contains(invocationIntent.getPackage())) {
+ List<ResolveInfo> dummyServices = new ArrayList<>();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = invocationIntent.getPackage();
+ serviceInfo.name = approvedComponent.getClassName();
+ serviceInfo.permission = service.getConfig().bindPermission;
+ resolveInfo.serviceInfo = serviceInfo;
+ dummyServices.add(resolveInfo);
+ return dummyServices;
+ }
+ }
+ return new ArrayList<>();
+ }
+ });
+
+ // Trigger package update
+ service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
+
+ assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent));
+ assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent));
+ }
+
+ @Test
public void testSetPackageOrComponentEnabled() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1915,7 +1973,7 @@
metaDataAutobindAllow.putBoolean(META_DATA_DEFAULT_AUTOBIND, true);
metaDatas.put(cn_allowed, metaDataAutobindAllow);
- mockServiceInfoWithMetaData(componentNames, service, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
service.addApprovedList(cn_allowed.flattenToString(), 0, true);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -1960,7 +2018,7 @@
metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false);
metaDatas.put(cn_disallowed, metaDataAutobindDisallow);
- mockServiceInfoWithMetaData(componentNames, service, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -1999,7 +2057,7 @@
metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false);
metaDatas.put(cn_disallowed, metaDataAutobindDisallow);
- mockServiceInfoWithMetaData(componentNames, service, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -2070,8 +2128,8 @@
}
private void mockServiceInfoWithMetaData(List<ComponentName> componentNames,
- ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas)
- throws RemoteException {
+ ManagedServices service, PackageManager packageManager,
+ ArrayMap<ComponentName, Bundle> metaDatas) throws RemoteException {
when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
(Answer<ServiceInfo>) invocation -> {
ComponentName invocationCn = invocation.getArgument(0);
@@ -2086,6 +2144,39 @@
return null;
}
);
+
+ // add components to queryIntentServicesAsUser response
+ final List<String> packages = new ArrayList<>();
+ for (ComponentName cn: componentNames) {
+ packages.add(cn.getPackageName());
+ }
+ ManagedServices.Config config = service.getConfig();
+ when(packageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())).
+ thenAnswer(new Answer<List<ResolveInfo>>() {
+ @Override
+ public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
+ throws Throwable {
+ Object[] args = invocationOnMock.getArguments();
+ Intent invocationIntent = (Intent) args[0];
+ if (invocationIntent != null) {
+ if (invocationIntent.getAction().equals(config.serviceInterface)
+ && packages.contains(invocationIntent.getPackage())) {
+ List<ResolveInfo> dummyServices = new ArrayList<>();
+ for (ComponentName cn: componentNames) {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = invocationIntent.getPackage();
+ serviceInfo.name = cn.getClassName();
+ serviceInfo.permission = service.getConfig().bindPermission;
+ resolveInfo.serviceInfo = serviceInfo;
+ dummyServices.add(resolveInfo);
+ }
+ return dummyServices;
+ }
+ }
+ return new ArrayList<>();
+ }
+ });
}
private void resetComponentsAndPackages() {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index 901c036..4f75931 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -20,7 +20,7 @@
import static android.os.VibrationAttributes.CATEGORY_UNKNOWN;
import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
-import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
+import static android.os.VibrationAttributes.USAGE_IME_FEEDBACK;
import static android.os.VibrationAttributes.USAGE_TOUCH;
import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
@@ -346,7 +346,7 @@
}
@Test
- public void testVibrationAttribute_keyboardCategoryOff_isIme_notUseKeyboardCategory() {
+ public void testVibrationAttribute_keyboardCategoryOff_isIme_useTouchUsage() {
mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
@@ -362,7 +362,7 @@
}
@Test
- public void testVibrationAttribute_keyboardCategoryOn_notIme_notUseKeyboardCategory() {
+ public void testVibrationAttribute_keyboardCategoryOn_notIme_useTouchUsage() {
mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
@@ -377,7 +377,7 @@
}
@Test
- public void testVibrationAttribute_keyboardCategoryOn_isIme_useKeyboardCategory() {
+ public void testVibrationAttribute_keyboardCategoryOn_isIme_useImeFeedbackUsage() {
mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
@@ -385,64 +385,14 @@
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
effectId, /* flags */ 0,
HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
- assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
- .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
+ assertWithMessage("Expected USAGE_IME_FEEDBACK for effect " + effectId)
+ .that(attrs.getUsage()).isEqualTo(USAGE_IME_FEEDBACK);
assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId)
.that(attrs.getCategory()).isEqualTo(CATEGORY_KEYBOARD);
}
}
@Test
- public void testVibrationAttribute_noFixAmplitude_notBypassIntensityScale() {
- mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
- mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
- mockKeyboardVibrationFixedAmplitude(-1);
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
-
- for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* flags */ 0,
- HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
- assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
- + effectId)
- .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse();
- }
- }
-
- @Test
- public void testVibrationAttribute_notIme_notBypassIntensityScale() {
- mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
- mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
- mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
-
- for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* flags */ 0, /* privFlags */ 0);
- assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
- + effectId)
- .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse();
- }
- }
-
- @Test
- public void testVibrationAttribute_fixAmplitude_isIme_bypassIntensityScale() {
- mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
- mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
- mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
-
- for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* flags */ 0,
- HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
- assertWithMessage("Expected FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
- + effectId)
- .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isTrue();
- }
- }
-
- @Test
public void testIsRestricted_biometricConstants_returnsTrue() {
HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index 60d8964..8d4a6aa 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -23,6 +23,7 @@
import static android.os.VibrationAttributes.USAGE_ALARM;
import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_IME_FEEDBACK;
import static android.os.VibrationAttributes.USAGE_MEDIA;
import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
@@ -893,6 +894,22 @@
}
@Test
+ public void getCurrentIntensity_ImeFeedbackValueReflectsToKeyboardVibrationSettings() {
+ setDefaultIntensity(USAGE_IME_FEEDBACK, VIBRATION_INTENSITY_MEDIUM);
+ setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_HIGH);
+
+ setKeyboardVibrationSettingsSupported(false);
+ mVibrationSettings.update();
+ assertEquals(VIBRATION_INTENSITY_HIGH,
+ mVibrationSettings.getCurrentIntensity(USAGE_IME_FEEDBACK));
+
+ setKeyboardVibrationSettingsSupported(true);
+ mVibrationSettings.update();
+ assertEquals(VIBRATION_INTENSITY_MEDIUM,
+ mVibrationSettings.getCurrentIntensity(USAGE_IME_FEEDBACK));
+ }
+
+ @Test
public void getFallbackEffect_returnsEffectsFromSettings() {
assertNotNull(mVibrationSettings.getFallbackEffect(VibrationEffect.EFFECT_TICK));
assertNotNull(mVibrationSettings.getFallbackEffect(VibrationEffect.EFFECT_TEXTURE_TICK));
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index bea6917..e411a17 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -154,6 +154,9 @@
private static final VibrationAttributes RINGTONE_ATTRS =
new VibrationAttributes.Builder().setUsage(
VibrationAttributes.USAGE_RINGTONE).build();
+ private static final VibrationAttributes IME_FEEDBACK_ATTRS =
+ new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_IME_FEEDBACK).build();
private static final VibrationAttributes UNKNOWN_ATTRS =
new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_UNKNOWN).build();
@@ -853,6 +856,7 @@
vibrate(service, VibrationEffect.createOneShot(2000, 200),
new VibrationAttributes.Builder().setUsage(
VibrationAttributes.USAGE_UNKNOWN).build());
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), IME_FEEDBACK_ATTRS);
InOrder inOrderVerifier = inOrder(mAppOpsManagerMock);
inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
@@ -868,6 +872,8 @@
anyInt(), anyString());
inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
}
@Test
@@ -1684,40 +1690,6 @@
}
@Test
- public void vibrate_withBypassScaleFlag_ignoresIntensitySettingsAndResolvesAmplitude()
- throws Exception {
- // Permission needed for bypassing user settings
- grantPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
-
- int defaultTouchIntensity =
- mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_TOUCH);
- // This will scale down touch vibrations.
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW
- ? defaultTouchIntensity - 1
- : defaultTouchIntensity);
-
- int defaultAmplitude = mContextSpy.getResources().getInteger(
- com.android.internal.R.integer.config_defaultVibrationAmplitude);
-
- mockVibrators(1);
- FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
- fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- VibratorManagerService service = createSystemReadyService();
-
- vibrateAndWaitUntilFinished(service,
- VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE),
- new VibrationAttributes.Builder()
- .setUsage(VibrationAttributes.USAGE_TOUCH)
- .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)
- .build());
-
- assertEquals(1, fakeVibrator.getAllEffectSegments().size());
-
- assertEquals(defaultAmplitude / 255f, fakeVibrator.getAmplitudes().get(0), 1e-5);
- }
-
- @Test
@RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
public void vibrate_withAdaptiveHaptics_appliesCorrectAdaptiveScales() throws Exception {
// Keep user settings the same as device default so only adaptive scale is applied.
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index ff1c6c8..d0080d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -102,6 +102,7 @@
import android.service.voice.IVoiceInteractionSession;
import android.util.Pair;
import android.util.Size;
+import android.view.Display;
import android.view.Gravity;
import android.view.RemoteAnimationAdapter;
import android.window.TaskFragmentOrganizerToken;
@@ -941,6 +942,91 @@
notNull() /* options */);
}
+
+ /**
+ * This test ensures that activity launch on a secondary display is allowed if the activity did
+ * not opt out from showing on remote devices.
+ */
+ @Test
+ public void testStartActivityOnVirtualDisplay() {
+ final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
+ false /* mockGetRootTask */);
+ starter.mRequest.activityInfo.flags |= ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
+
+ // Create a virtual display at bottom.
+ final TestDisplayContent secondaryDisplay =
+ new TestDisplayContent.Builder(mAtm, 1000, 1500)
+ .setType(Display.TYPE_VIRTUAL)
+ .setPosition(POSITION_BOTTOM).build();
+ final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea();
+ final Task stack = secondaryTaskContainer.createRootTask(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+ // Create an activity record on the top of secondary display.
+ final ActivityRecord topActivityOnSecondaryDisplay = createSingleTaskActivityOn(stack);
+
+ // Put an activity on default display as the top focused activity.
+ new ActivityBuilder(mAtm).setCreateTask(true).build();
+
+ // Start activity with the same intent as {@code topActivityOnSecondaryDisplay}
+ // on secondary display.
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setLaunchDisplayId(secondaryDisplay.mDisplayId);
+ final int result = starter.setReason("testStartActivityOnVirtualDisplay")
+ .setIntent(topActivityOnSecondaryDisplay.intent)
+ .setActivityOptions(options.toBundle())
+ .execute();
+
+ // Ensure result is delivering intent to top.
+ assertEquals(START_DELIVERED_TO_TOP, result);
+
+ // Ensure secondary display only creates one stack.
+ verify(secondaryTaskContainer, times(1)).createRootTask(anyInt(), anyInt(), anyBoolean());
+ }
+
+ /**
+ * This test ensures that activity launch on a secondary display is disallowed if the activity
+ * opted out from showing on remote devices.
+ */
+ @EnableFlags(android.companion.virtualdevice.flags.Flags
+ .FLAG_ENFORCE_REMOTE_DEVICE_OPT_OUT_ON_ALL_VIRTUAL_DISPLAYS)
+ @Test
+ public void testStartOptedOutActivityOnVirtualDisplay() {
+ final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
+ false /* mockGetRootTask */);
+ starter.mRequest.activityInfo.flags &= ~ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
+
+ // Create a virtual display at bottom.
+ final TestDisplayContent secondaryDisplay =
+ new TestDisplayContent.Builder(mAtm, 1000, 1500)
+ .setType(Display.TYPE_VIRTUAL)
+ .setPosition(POSITION_BOTTOM).build();
+ final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea();
+ final Task stack = secondaryTaskContainer.createRootTask(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+ // Create an activity record on the top of secondary display.
+ final ActivityRecord topActivityOnSecondaryDisplay = createSingleTaskActivityOn(stack);
+
+ // Put an activity on default display as the top focused activity.
+ new ActivityBuilder(mAtm).setCreateTask(true).build();
+
+ // Start activity with the same intent as {@code topActivityOnSecondaryDisplay}
+ // on secondary display.
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setLaunchDisplayId(secondaryDisplay.mDisplayId);
+ final int result = starter.setReason("testStartOptedOutActivityOnVirtualDisplay")
+ .setIntent(topActivityOnSecondaryDisplay.intent)
+ .setActivityOptions(options.toBundle())
+ .execute();
+
+ // Ensure result is canceled.
+ assertEquals(START_CANCELED, result);
+
+ // Ensure secondary display only creates one stack.
+ verify(secondaryTaskContainer, times(1)).createRootTask(anyInt(), anyInt(), anyBoolean());
+ }
+
@Test
public void testWasVisibleInRestartAttempt() {
final ActivityStarter starter = prepareStarter(
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/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
index 57118f2..f843386 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -63,11 +63,6 @@
private final Message mScreenUnblocker = mock(Message.class);
- @Override
- protected void onBeforeSystemServicesCreated() {
- mSetFlagsRule.enableFlags(Flags.FLAG_DEFER_DISPLAY_UPDATES);
- }
-
@Before
public void before() {
doReturn(true).when(mDisplayContent).getLastHasContent();
diff --git a/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
deleted file mode 100644
index 78509db..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
-import static com.android.server.wm.DeviceStateController.DeviceState.REAR;
-import static com.android.server.wm.DeviceStateController.DeviceState.FOLDED;
-import static com.android.server.wm.DeviceStateController.DeviceState.HALF_FOLDED;
-import static com.android.server.wm.DeviceStateController.DeviceState.OPEN;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.when;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for the {@link WindowToken} class.
- *
- * Build/Install/Run:
- * atest WmTests:PhysicalDisplaySwitchTransitionLauncherTest
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class PhysicalDisplaySwitchTransitionLauncherTest extends WindowTestsBase {
-
- @Mock
- Context mContext;
- @Mock
- Resources mResources;
- @Mock
- BLASTSyncEngine mSyncEngine;
-
- WindowTestsBase.TestTransitionPlayer mPlayer;
- TransitionController mTransitionController;
- DisplayContent mDisplayContent;
-
- private PhysicalDisplaySwitchTransitionLauncher mTarget;
- private float mOriginalAnimationScale;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mTransitionController = new WindowTestsBase.TestTransitionController(mAtm);
- mTransitionController.setSyncEngine(mSyncEngine);
- mPlayer = new WindowTestsBase.TestTransitionPlayer(
- mTransitionController, mAtm.mWindowOrganizerController);
- when(mContext.getResources()).thenReturn(mResources);
- mDisplayContent = new TestDisplayContent.Builder(mAtm, 100, 150).build();
- mTarget = new PhysicalDisplaySwitchTransitionLauncher(mDisplayContent, mAtm, mContext,
- mTransitionController);
- mOriginalAnimationScale = ValueAnimator.getDurationScale();
- }
-
- @After
- public void after() {
- ValueAnimator.setDurationScale(mOriginalAnimationScale);
- }
-
- @Test
- public void testDisplaySwitchAfterUnfoldToOpen_animationsEnabled_requestsTransition() {
- givenAllAnimationsEnabled();
- mTarget.foldStateChanged(FOLDED);
-
- mTarget.foldStateChanged(OPEN);
- final Rect origBounds = new Rect();
- mDisplayContent.getBounds(origBounds);
- origBounds.offsetTo(0, 0);
- mTarget.requestDisplaySwitchTransitionIfNeeded(
- mDisplayContent.getDisplayId(),
- origBounds.width(),
- origBounds.height(),
- /* newDisplayWidth= */ 200,
- /* newDisplayHeight= */ 250
- );
-
- assertNotNull(mPlayer.mLastRequest);
- assertEquals(mDisplayContent.getDisplayId(),
- mPlayer.mLastRequest.getDisplayChange().getDisplayId());
- assertEquals(origBounds, mPlayer.mLastRequest.getDisplayChange().getStartAbsBounds());
- assertEquals(new Rect(0, 0, 200, 250),
- mPlayer.mLastRequest.getDisplayChange().getEndAbsBounds());
- }
-
- @Test
- public void testDisplaySwitchAfterFolding_animationEnabled_doesNotRequestTransition() {
- givenAllAnimationsEnabled();
- mTarget.foldStateChanged(OPEN);
-
- mTarget.foldStateChanged(FOLDED);
- requestDisplaySwitch();
-
- assertTransitionNotRequested();
- }
-
- @Test
- public void testDisplaySwitchAfterUnfoldingToHalf_animationEnabled_requestsTransition() {
- givenAllAnimationsEnabled();
- mTarget.foldStateChanged(FOLDED);
-
- mTarget.foldStateChanged(HALF_FOLDED);
- requestDisplaySwitch();
-
- assertTransitionRequested();
- }
-
- @Test
- public void testDisplaySwitchSecondTimeAfterUnfolding_animationEnabled_noTransition() {
- givenAllAnimationsEnabled();
- mTarget.foldStateChanged(FOLDED);
- mTarget.foldStateChanged(OPEN);
- requestDisplaySwitch();
- mPlayer.mLastRequest = null;
-
- requestDisplaySwitch();
-
- assertTransitionNotRequested();
- }
-
-
- @Test
- public void testDisplaySwitchAfterGoingToRearAndBack_animationEnabled_noTransition() {
- givenAllAnimationsEnabled();
- mTarget.foldStateChanged(OPEN);
-
- mTarget.foldStateChanged(REAR);
- mTarget.foldStateChanged(OPEN);
- requestDisplaySwitch();
-
- assertTransitionNotRequested();
- }
-
- @Test
- public void testDisplaySwitchAfterUnfoldingAndFolding_animationEnabled_noTransition() {
- givenAllAnimationsEnabled();
- mTarget.foldStateChanged(FOLDED);
- mTarget.foldStateChanged(OPEN);
- // No request display switch event (simulate very fast fold after unfold, even before
- // the displays switched)
- mTarget.foldStateChanged(FOLDED);
-
- requestDisplaySwitch();
-
- assertTransitionNotRequested();
- }
-
- @Test
- public void testDisplaySwitch_whenShellTransitionsNotEnabled_noTransition() {
- givenAllAnimationsEnabled();
- givenShellTransitionsEnabled(false);
- mTarget.foldStateChanged(FOLDED);
-
- mTarget.foldStateChanged(OPEN);
- requestDisplaySwitch();
-
- assertTransitionNotRequested();
- }
-
- @Test
- public void testDisplaySwitch_whenAnimationsDisabled_noTransition() {
- givenAllAnimationsEnabled();
- givenAnimationsEnabled(false);
- mTarget.foldStateChanged(FOLDED);
-
- mTarget.foldStateChanged(OPEN);
- requestDisplaySwitch();
-
- assertTransitionNotRequested();
- }
-
- @Test
- public void testDisplaySwitch_whenUnfoldAnimationDisabled_noTransition() {
- givenAllAnimationsEnabled();
- givenUnfoldTransitionEnabled(false);
- mTarget.foldStateChanged(FOLDED);
-
- mTarget.foldStateChanged(OPEN);
- requestDisplaySwitch();
-
- assertTransitionNotRequested();
- }
-
- @Test
- public void testDisplaySwitchAfterUnfolding_otherCollectingTransition_collectsDisplaySwitch() {
- givenAllAnimationsEnabled();
- mTarget.foldStateChanged(FOLDED);
-
- mTarget.foldStateChanged(OPEN);
- requestDisplaySwitch();
-
- // Collects to the current transition
- assertTrue(mTransitionController.getCollectingTransition().mParticipants.contains(
- mDisplayContent));
- }
-
-
- @Test
- public void testDisplaySwitch_whenNoContentInDisplayContent_noTransition() {
- givenAllAnimationsEnabled();
- givenDisplayContentHasContent(false);
- mTarget.foldStateChanged(FOLDED);
-
- mTarget.foldStateChanged(OPEN);
- requestDisplaySwitch();
-
- assertTransitionNotRequested();
- }
-
- private void assertTransitionRequested() {
- assertNotNull(mPlayer.mLastRequest);
- }
-
- private void assertTransitionNotRequested() {
- assertNull(mPlayer.mLastRequest);
- }
-
- private void requestDisplaySwitch() {
- mTarget.requestDisplaySwitchTransitionIfNeeded(
- mDisplayContent.getDisplayId(),
- mDisplayContent.getBounds().width(),
- mDisplayContent.getBounds().height(),
- /* newDisplayWidth= */ 200,
- /* newDisplayHeight= */ 250
- );
- }
-
- private void givenAllAnimationsEnabled() {
- givenAnimationsEnabled(true);
- givenUnfoldTransitionEnabled(true);
- givenShellTransitionsEnabled(true);
- givenDisplayContentHasContent(true);
- }
-
- private void givenUnfoldTransitionEnabled(boolean enabled) {
- when(mResources.getBoolean(config_unfoldTransitionEnabled)).thenReturn(enabled);
- }
-
- private void givenAnimationsEnabled(boolean enabled) {
- ValueAnimator.setDurationScale(enabled ? 1.0f : 0.0f);
- }
-
- private void givenShellTransitionsEnabled(boolean enabled) {
- if (enabled) {
- mTransitionController.registerTransitionPlayer(mPlayer, null /* proc */);
- } else {
- mTransitionController.unregisterTransitionPlayer(mPlayer);
- }
- }
-
- private void givenDisplayContentHasContent(boolean hasContent) {
- when(mDisplayContent.getLastHasContent()).thenReturn(hasContent);
- }
-}
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