Merge "Add input team as owners LetterboxScrollProcessor* files" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 836d4e99..664dfe9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4919,6 +4919,7 @@
method public int getPendingIntentBackgroundActivityStartMode();
method public int getPendingIntentCreatorBackgroundActivityStartMode();
method public int getSplashScreenStyle();
+ method @FlaggedApi("com.android.window.flags.touch_pass_through_opt_in") public boolean isAllowPassThroughOnTouchOutside();
method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
method public boolean isShareIdentityEnabled();
method public static android.app.ActivityOptions makeBasic();
@@ -4932,6 +4933,7 @@
method public static android.app.ActivityOptions makeTaskLaunchBehind();
method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
method public void requestUsageTimeReport(android.app.PendingIntent);
+ method @FlaggedApi("com.android.window.flags.touch_pass_through_opt_in") public void setAllowPassThroughOnTouchOutside(boolean);
method public android.app.ActivityOptions setAppVerificationBundle(android.os.Bundle);
method public android.app.ActivityOptions setLaunchBounds(@Nullable android.graphics.Rect);
method public android.app.ActivityOptions setLaunchDisplayId(int);
@@ -8775,6 +8777,11 @@
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
method @Deprecated @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+ method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
+ field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
+ field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2
+ field public static final int APP_FUNCTION_STATE_ENABLED = 1; // 0x1
}
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
@@ -8816,6 +8823,7 @@
field public static final String PROPERTY_RETURN_VALUE = "returnValue";
field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
field public static final int RESULT_DENIED = 1; // 0x1
+ field public static final int RESULT_DISABLED = 6; // 0x6
field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
field public static final int RESULT_OK = 0; // 0x0
@@ -46698,6 +46706,8 @@
field public static final int TYPE_IMS = 64; // 0x40
field public static final int TYPE_MCX = 1024; // 0x400
field public static final int TYPE_MMS = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final int TYPE_OEM_PAID = 65536; // 0x10000
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final int TYPE_OEM_PRIVATE = 131072; // 0x20000
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int TYPE_RCS = 32768; // 0x8000
field public static final int TYPE_SUPL = 4; // 0x4
field public static final int TYPE_VSIM = 4096; // 0x1000
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a1561c2..bc34f5b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -15888,6 +15888,8 @@
field public static final String TYPE_IMS_STRING = "ims";
field public static final String TYPE_MCX_STRING = "mcx";
field public static final String TYPE_MMS_STRING = "mms";
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final String TYPE_OEM_PAID_STRING = "oem_paid";
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final String TYPE_OEM_PRIVATE_STRING = "oem_private";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String TYPE_RCS_STRING = "rcs";
field public static final String TYPE_SUPL_STRING = "supl";
field public static final String TYPE_VSIM_STRING = "vsim";
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 92bca3c..9904632 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -21,14 +21,52 @@
"**/*.aidl",
":framework-nfc-non-updatable-sources",
":messagequeue-gen",
+ ":ranging_stack_mock_initializer",
],
// Exactly one MessageQueue.java will be added to srcs by messagequeue-gen
exclude_srcs: [
"android/os/*MessageQueue/**/*.java",
+ "android/ranging/**/*.java",
],
visibility: ["//frameworks/base"],
}
+//Mock to allow service registry for ranging stack.
+//TODO(b/331206299): Remove this after RELEASE_RANGING_STACK is ramped up to next.
+soong_config_module_type {
+ name: "ranging_stack_framework_mock_init",
+ module_type: "genrule",
+ config_namespace: "bootclasspath",
+ bool_variables: [
+ "release_ranging_stack",
+ ],
+ properties: [
+ "srcs",
+ "cmd",
+ "out",
+ ],
+}
+
+// The actual RangingFrameworkInitializer is present in packages/modules/Uwb/ranging/framework.
+// Mock RangingFrameworkInitializer does nothing and allows to successfully build
+// SystemServiceRegistry after registering for system service in SystemServiceRegistry both with
+// and without build flag RELEASE_RANGING_STACK enabled.
+ranging_stack_framework_mock_init {
+ name: "ranging_stack_mock_initializer",
+ soong_config_variables: {
+ release_ranging_stack: {
+ cmd: "touch $(out)",
+ // Adding an empty file as out is mandatory.
+ out: ["android/ranging/empty_ranging_fw.txt"],
+ conditions_default: {
+ srcs: ["android/ranging/mock/RangingFrameworkInitializer.java"],
+ cmd: "mkdir -p android/ranging/; cp $(in) $(out);",
+ out: ["android/ranging/RangingFrameworkInitializer.java"],
+ },
+ },
+ },
+}
+
// Add selected MessageQueue.java implementation to srcs
soong_config_module_type {
name: "release_package_messagequeue_implementation_srcs",
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 91aa225..0d183c7 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -26,6 +26,7 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -453,6 +454,10 @@
private static final String KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE =
"android.activity.pendingIntentCreatorBackgroundActivityStartMode";
+ /** See {@link #setAllowPassThroughOnTouchOutside(boolean)}. */
+ private static final String KEY_ALLOW_PASS_THROUGH_ON_TOUCH_OUTSIDE =
+ "android.activity.allowPassThroughOnTouchOutside";
+
/**
* @see #setLaunchCookie
* @hide
@@ -554,6 +559,7 @@
private int mPendingIntentCreatorBackgroundActivityStartMode =
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
private boolean mDisableStartingWindow;
+ private boolean mAllowPassThroughOnTouchOutside;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -1416,6 +1422,7 @@
KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE,
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
mDisableStartingWindow = opts.getBoolean(KEY_DISABLE_STARTING_WINDOW);
+ mAllowPassThroughOnTouchOutside = opts.getBoolean(KEY_ALLOW_PASS_THROUGH_ON_TOUCH_OUTSIDE);
mAnimationAbortListener = IRemoteCallback.Stub.asInterface(
opts.getBinder(KEY_ANIM_ABORT_LISTENER));
}
@@ -1839,6 +1846,39 @@
&& mLaunchIntoPipParams.isLaunchIntoPip();
}
+ /**
+ * Returns whether the source activity allows the overlaying activities from the to-be-launched
+ * app to pass through touch events to it when touches fall outside the content window.
+ *
+ * @see #setAllowPassThroughOnTouchOutside(boolean)
+ */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_TOUCH_PASS_THROUGH_OPT_IN)
+ public boolean isAllowPassThroughOnTouchOutside() {
+ return mAllowPassThroughOnTouchOutside;
+ }
+
+ /**
+ * Sets whether the source activity allows the overlaying activities from the to-be-launched
+ * app to pass through touch events to it when touches fall outside the content window.
+ *
+ * <p> By default, touches that fall on a translucent non-touchable area of an overlaying
+ * activity window are blocked from passing through to the activity below (source activity),
+ * unless the overlaying activity is from the same UID as the source activity. The source
+ * activity may use this method to opt in and allow the overlaying activities from the
+ * to-be-launched app to pass through touches to itself. The source activity needs to ensure
+ * that it trusts the overlaying activity and its content is not vulnerable to UI redressing
+ * attacks. The flag is ignored if the context calling
+ * {@link Context#startActivity(Intent, Bundle)} is not an activity.
+ *
+ * <p> For backward compatibility, apps with target SDK 35 and below may still receive
+ * pass-through touches without opt-in if the cross-uid activity is launched by the source
+ * activity.
+ */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_TOUCH_PASS_THROUGH_OPT_IN)
+ public void setAllowPassThroughOnTouchOutside(boolean allowed) {
+ mAllowPassThroughOnTouchOutside = allowed;
+ }
+
/** @hide */
public int getLaunchActivityType() {
return mLaunchActivityType;
@@ -2520,6 +2560,10 @@
if (mDisableStartingWindow) {
b.putBoolean(KEY_DISABLE_STARTING_WINDOW, mDisableStartingWindow);
}
+ if (mAllowPassThroughOnTouchOutside) {
+ b.putBoolean(KEY_ALLOW_PASS_THROUGH_ON_TOUCH_OUTSIDE,
+ mAllowPassThroughOnTouchOutside);
+ }
b.putBinder(KEY_ANIM_ABORT_LISTENER,
mAnimationAbortListener != null ? mAnimationAbortListener.asBinder() : null);
return b;
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index b781ce5..f21c3e8 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -493,6 +493,9 @@
* {@link Environment#getExternalStoragePublicDirectory(String)} with
* {@link Environment#DIRECTORY_DOWNLOADS}).
*
+ * All non-visible downloads that are not modified in the last 7 days will be deleted during
+ * idle runs.
+ *
* @param uri a file {@link Uri} indicating the destination for the downloaded file.
* @return this object
*/
@@ -796,7 +799,9 @@
* public Downloads directory (as returned by
* {@link Environment#getExternalStoragePublicDirectory(String)} with
* {@link Environment#DIRECTORY_DOWNLOADS}) will be visible in system's Downloads UI
- * and the rest will not be visible.
+ * and the rest will not be visible. All non-visible downloads that are not modified
+ * in the last 7 days will be deleted during idle runs.
+ *
* (e.g. {@link Context#getExternalFilesDir(String)}) will not be visible.
*/
@Deprecated
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a39f216..c21fe0e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1619,22 +1619,6 @@
public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
/**
- * {@link #extras} key: {@link Icon} of an image used as an overlay Icon on
- * {@link Notification#mLargeIcon} for {@link EnRouteStyle} notifications.
- * This extra is an {@code Icon}.
- * @hide
- */
- @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
- public static final String EXTRA_ENROUTE_OVERLAY_ICON = "android.enrouteOverlayIcon";
-
- /**
- * {@link #extras} key: text used as a sub-text for the largeIcon of
- * {@link EnRouteStyle} notification. This extra is a {@code CharSequence}.
- * @hide
- */
- @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
- public static final String EXTRA_ENROUTE_LARGE_ICON_SUBTEXT = "android.enrouteLargeIconSubText";
- /**
* {@link #extras} key: whether the notification should be colorized as
* supplied to {@link Builder#setColorized(boolean)}.
*/
@@ -3152,7 +3136,6 @@
}
if (Flags.apiRichOngoing()) {
- visitIconUri(visitor, extras.getParcelable(EXTRA_ENROUTE_OVERLAY_ICON, Icon.class));
visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_TRACKER_ICON, Icon.class));
visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_START_ICON, Icon.class));
visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_END_ICON, Icon.class));
@@ -11173,145 +11156,6 @@
}
/**
- * TODO(b/360827871): Make EnRouteStyle public.
- * A style used to represent the progress of a real-world journey with a known destination.
- * For example:
- * <ul>
- * <li>Delivery tracking</li>
- * <li>Ride progress</li>
- * <li>Flight tracking</li>
- * </ul>
- *
- * The exact fields from {@link Notification} that are shown with this style may vary by
- * the surface where this update appears, but the following fields are recommended:
- * <ul>
- * <li>{@link Notification.Builder#setContentTitle}</li>
- * <li>{@link Notification.Builder#setContentText}</li>
- * <li>{@link Notification.Builder#setSubText}</li>
- * <li>{@link Notification.Builder#setLargeIcon}</li>
- * <li>{@link Notification.Builder#setProgress}</li>
- * <li>{@link Notification.Builder#setWhen} - This should be the future time of the next,
- * final, or most important stop on this journey.</li>
- * </ul>
- * @hide
- */
- @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
- public static class EnRouteStyle extends Notification.Style {
-
- @Nullable
- private Icon mOverlayIcon = null;
-
- @Nullable
- private CharSequence mLargeIconSubText = null;
-
- public EnRouteStyle() {
- }
-
- /**
- * Returns the overlay icon to be displayed on {@link Notification#mLargeIcon}.
- * @see EnRouteStyle#setOverlayIcon
- */
- @Nullable
- public Icon getOverlayIcon() {
- return mOverlayIcon;
- }
-
- /**
- * Optional icon to be displayed on {@link Notification#mLargeIcon}.
- *
- * This image will be cropped to a circle and will obscure
- * a semicircle of the right side of the large icon.
- */
- @NonNull
- public EnRouteStyle setOverlayIcon(@Nullable Icon overlayIcon) {
- mOverlayIcon = overlayIcon;
- return this;
- }
-
- /**
- * Returns the sub-text for {@link Notification#mLargeIcon}.
- * @see EnRouteStyle#setLargeIconSubText
- */
- @Nullable
- public CharSequence getLargeIconSubText() {
- return mLargeIconSubText;
- }
-
- /**
- * Optional text which generally related to
- * the {@link Notification.Builder#setLargeIcon} or {@link #setOverlayIcon} or both.
- */
- @NonNull
- public EnRouteStyle setLargeIconSubText(@Nullable CharSequence largeIconSubText) {
- mLargeIconSubText = stripStyling(largeIconSubText);
- return this;
- }
-
- /**
- * @hide
- */
- @Override
- public boolean areNotificationsVisiblyDifferent(Style other) {
- if (other == null || getClass() != other.getClass()) {
- return true;
- }
-
- final EnRouteStyle enRouteStyle = (EnRouteStyle) other;
- return !Objects.equals(mOverlayIcon, enRouteStyle.mOverlayIcon)
- || !Objects.equals(mLargeIconSubText, enRouteStyle.mLargeIconSubText);
- }
-
- /**
- * @hide
- */
- @Override
- public void addExtras(Bundle extras) {
- super.addExtras(extras);
- extras.putParcelable(EXTRA_ENROUTE_OVERLAY_ICON, mOverlayIcon);
- extras.putCharSequence(EXTRA_ENROUTE_LARGE_ICON_SUBTEXT, mLargeIconSubText);
- }
-
- /**
- * @hide
- */
- @Override
- protected void restoreFromExtras(Bundle extras) {
- super.restoreFromExtras(extras);
- mOverlayIcon = extras.getParcelable(EXTRA_ENROUTE_OVERLAY_ICON, Icon.class);
- mLargeIconSubText = extras.getCharSequence(EXTRA_ENROUTE_LARGE_ICON_SUBTEXT);
- }
-
- /**
- * @hide
- */
- @Override
- public void purgeResources() {
- super.purgeResources();
- if (mOverlayIcon != null) {
- mOverlayIcon.convertToAshmem();
- }
- }
-
- /**
- * @hide
- */
- @Override
- public void reduceImageSizes(Context context) {
- super.reduceImageSizes(context);
- if (mOverlayIcon != null) {
- final Resources resources = context.getResources();
- final boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
-
- int rightIconSize = resources.getDimensionPixelSize(isLowRam
- ? R.dimen.notification_right_icon_size_low_ram
- : R.dimen.notification_right_icon_size);
- mOverlayIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
- }
- }
- }
-
-
- /**
* A Notification Style used to to define a notification whose expanded state includes
* a highly customizable progress bar with segments, steps, a custom tracker icon,
* and custom icons at the start and end of the progress bar.
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index c13a58f..ea4148c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -230,6 +230,7 @@
import android.print.PrintManager;
import android.provider.E2eeContactKeysManager;
import android.provider.ProviderFrameworkInitializer;
+import android.ranging.RangingFrameworkInitializer;
import android.safetycenter.SafetyCenterFrameworkInitializer;
import android.scheduling.SchedulingFrameworkInitializer;
import android.security.FileIntegrityManager;
@@ -1825,6 +1826,12 @@
if (android.webkit.Flags.updateServiceIpcWrapper()) {
WebViewBootstrapFrameworkInitializer.registerServiceWrappers();
}
+ // This is guarded by aconfig flag "com.android.ranging.flags.ranging_stack_enabled"
+ // when the build flag RELEASE_RANGING_STACK is enabled. When disabled, this calls the
+ // mock RangingFrameworkInitializer#registerServiceWrappers which is no-op. As the
+ // aconfig lib for ranging module is built only if RELEASE_RANGING_STACK is enabled,
+ // flagcannot be added here.
+ RangingFrameworkInitializer.registerServiceWrappers();
} finally {
// If any of the above code throws, we're in a pretty bad shape and the process
// will likely crash, but we'll reset it just in case there's an exception handler...
diff --git a/core/java/android/app/admin/PolicySizeVerifier.java b/core/java/android/app/admin/PolicySizeVerifier.java
index 7f8e50e..1e03e1f 100644
--- a/core/java/android/app/admin/PolicySizeVerifier.java
+++ b/core/java/android/app/admin/PolicySizeVerifier.java
@@ -22,7 +22,9 @@
import android.os.PersistableBundle;
import com.android.internal.util.Preconditions;
+import com.android.modules.utils.ModifiedUtf8;
+import java.io.UTFDataFormatException;
import java.util.ArrayDeque;
import java.util.Queue;
@@ -33,8 +35,6 @@
*/
public class PolicySizeVerifier {
- // Binary XML serializer doesn't support longer strings
- public static final int MAX_POLICY_STRING_LENGTH = 65535;
// FrameworkParsingPackageUtils#MAX_FILE_NAME_SIZE, Android packages are used in dir names.
public static final int MAX_PACKAGE_NAME_LENGTH = 223;
@@ -47,8 +47,11 @@
* Throw if string argument is too long to be serialized.
*/
public static void enforceMaxStringLength(String str, String argName) {
- Preconditions.checkArgument(
- str.length() <= MAX_POLICY_STRING_LENGTH, argName + " loo long");
+ try {
+ long len = ModifiedUtf8.countBytes(str, /* throw error if too long */ true);
+ } catch (UTFDataFormatException e) {
+ throw new IllegalArgumentException(argName + " too long");
+ }
}
/**
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index 216ba5d..6797a51 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -22,15 +22,21 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.UserHandleAware;
+import android.app.appsearch.AppSearchManager;
import android.content.Context;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.ParcelableException;
import android.os.RemoteException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -45,10 +51,44 @@
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
@SystemService(Context.APP_FUNCTION_SERVICE)
public final class AppFunctionManager {
+
+ /**
+ * The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset
+ * enabled state to the default value.
+ */
+ public static final int APP_FUNCTION_STATE_DEFAULT = 0;
+
+ /**
+ * The app function is enabled. To enable an app function, call {@link #setAppFunctionEnabled}
+ * with this value.
+ */
+ public static final int APP_FUNCTION_STATE_ENABLED = 1;
+
+ /**
+ * The app function is disabled. To disable an app function, call {@link #setAppFunctionEnabled}
+ * with this value.
+ */
+ public static final int APP_FUNCTION_STATE_DISABLED = 2;
+
private final IAppFunctionManager mService;
private final Context mContext;
/**
+ * The enabled state of the app function.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"APP_FUNCTION_STATE_"},
+ value = {
+ APP_FUNCTION_STATE_DEFAULT,
+ APP_FUNCTION_STATE_ENABLED,
+ APP_FUNCTION_STATE_DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EnabledState {}
+
+ /**
* Creates an instance.
*
* @param service An interface to the backing service.
@@ -164,4 +204,118 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Returns a boolean through a callback, indicating whether the app function is enabled.
+ *
+ * <p>* This method can only check app functions owned by the caller, or those where the caller
+ * has visibility to the owner package and holds either the {@link
+ * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
+ * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
+ *
+ * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+ *
+ * <ul>
+ * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+ * have access to it.
+ * </ul>
+ *
+ * @param functionIdentifier the identifier of the app function to check (unique within the
+ * target package) and in most cases, these are automatically generated by the AppFunctions
+ * SDK
+ * @param targetPackage the package name of the app function's owner
+ * @param executor the executor to run the request
+ * @param callback the callback to receive the function enabled check result
+ */
+ public void isAppFunctionEnabled(
+ @NonNull String functionIdentifier,
+ @NonNull String targetPackage,
+ @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+ Objects.requireNonNull(functionIdentifier);
+ Objects.requireNonNull(targetPackage);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ AppSearchManager appSearchManager = mContext.getSystemService(AppSearchManager.class);
+ if (appSearchManager == null) {
+ callback.onError(new IllegalStateException("Failed to get AppSearchManager."));
+ return;
+ }
+
+ AppFunctionManagerHelper.isAppFunctionEnabled(
+ functionIdentifier, targetPackage, appSearchManager, executor, callback);
+ }
+
+ /**
+ * Sets the enabled state of the app function owned by the calling package.
+ *
+ * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+ *
+ * <ul>
+ * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+ * have access to it.
+ * </ul>
+ *
+ * @param functionIdentifier the identifier of the app function to enable (unique within the
+ * calling package). In most cases, identifiers are automatically generated by the
+ * AppFunctions SDK
+ * @param newEnabledState the new state of the app function
+ * @param executor the executor to run the callback
+ * @param callback the callback to receive the result of the function enablement. The call was
+ * successful if no exception was thrown.
+ */
+ @UserHandleAware
+ public void setAppFunctionEnabled(
+ @NonNull String functionIdentifier,
+ @EnabledState int newEnabledState,
+ @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, Exception> callback) {
+ Objects.requireNonNull(functionIdentifier);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback);
+ try {
+ mService.setAppFunctionEnabled(
+ mContext.getPackageName(),
+ functionIdentifier,
+ mContext.getUser(),
+ newEnabledState,
+ callbackWrapper);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static class CallbackWrapper extends IAppFunctionEnabledCallback.Stub {
+
+ private final OutcomeReceiver<Void, Exception> mCallback;
+ private final Executor mExecutor;
+
+ CallbackWrapper(
+ @NonNull Executor callbackExecutor,
+ @NonNull OutcomeReceiver<Void, Exception> callback) {
+ mCallback = callback;
+ mExecutor = callbackExecutor;
+ }
+
+ @Override
+ public void onSuccess() {
+ mExecutor.execute(() -> mCallback.onResult(null));
+ }
+
+ @Override
+ public void onError(@NonNull ParcelableException exception) {
+ mExecutor.execute(() -> {
+ if (IllegalArgumentException.class.isAssignableFrom(
+ exception.getCause().getClass())) {
+ mCallback.onError((IllegalArgumentException) exception.getCause());
+ } else if (SecurityException.class.isAssignableFrom(
+ exception.getCause().getClass())) {
+ mCallback.onError((SecurityException) exception.getCause());
+ } else {
+ mCallback.onError(exception);
+ }
+ });
+ }
+ }
}
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
index d6f45e4..fe2db49 100644
--- a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
@@ -22,7 +22,7 @@
import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_ENABLED_BY_DEFAULT;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
-import android.annotation.CallbackExecutor;
+import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchManager;
@@ -33,6 +33,7 @@
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchSpec;
import android.os.OutcomeReceiver;
+import android.text.TextUtils;
import java.io.IOException;
import java.util.List;
@@ -50,73 +51,69 @@
/**
* Returns (through a callback) a boolean indicating whether the app function is enabled.
*
- * <p>This method can only check app functions that are owned by the caller owned by packages
- * visible to the caller.
+ * This method can only check app functions owned by the caller, or those where the caller
+ * has visibility to the owner package and holds either the {@link
+ * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
+ * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
*
* <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
*
* <ul>
- * <li>{@link IllegalArgumentException}, if the function is not found
- * <li>{@link SecurityException}, if the caller does not have permission to query the target
- * package
+ * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+ * have access to it.
* </ul>
*
* @param functionIdentifier the identifier of the app function to check (unique within the
- * target package) and in most cases, these are automatically generated by the AppFunctions
- * SDK
- * @param targetPackage the package name of the app function's owner
- * @param appSearchExecutor the executor to run the metadata search mechanism through AppSearch
- * @param callbackExecutor the executor to run the callback
- * @param callback the callback to receive the function enabled check result
+ * target package) and in most cases, these are automatically
+ * generated by the AppFunctions
+ * SDK
+ * @param targetPackage the package name of the app function's owner
+ * @param executor executor the executor to run the request
+ * @param callback the callback to receive the function enabled check result
* @hide
*/
public static void isAppFunctionEnabled(
@NonNull String functionIdentifier,
@NonNull String targetPackage,
@NonNull AppSearchManager appSearchManager,
- @NonNull Executor appSearchExecutor,
- @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull Executor executor,
@NonNull OutcomeReceiver<Boolean, Exception> callback) {
Objects.requireNonNull(functionIdentifier);
Objects.requireNonNull(targetPackage);
Objects.requireNonNull(appSearchManager);
- Objects.requireNonNull(appSearchExecutor);
- Objects.requireNonNull(callbackExecutor);
+ Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
appSearchManager.createGlobalSearchSession(
- appSearchExecutor,
+ executor,
(searchSessionResult) -> {
if (!searchSessionResult.isSuccess()) {
- callbackExecutor.execute(
- () ->
- callback.onError(
- failedResultToException(searchSessionResult)));
+ callback.onError(failedResultToException(searchSessionResult));
return;
}
try (GlobalSearchSession searchSession = searchSessionResult.getResultValue()) {
SearchResults results =
searchJoinedStaticWithRuntimeAppFunctions(
- searchSession, targetPackage, functionIdentifier);
+ Objects.requireNonNull(searchSession),
+ targetPackage,
+ functionIdentifier);
results.getNextPage(
- appSearchExecutor,
- listAppSearchResult ->
- callbackExecutor.execute(
- () -> {
- if (listAppSearchResult.isSuccess()) {
- callback.onResult(
- getEnabledStateFromSearchResults(
- Objects.requireNonNull(
- listAppSearchResult
+ executor,
+ listAppSearchResult -> {
+ if (listAppSearchResult.isSuccess()) {
+ callback.onResult(
+ getEffectiveEnabledStateFromSearchResults(
+ Objects.requireNonNull(
+ listAppSearchResult
.getResultValue())));
- } else {
- callback.onError(
- failedResultToException(
- listAppSearchResult));
- }
- }));
+ } else {
+ callback.onError(
+ failedResultToException(listAppSearchResult));
+ }
+ });
+ results.close();
} catch (Exception e) {
- callbackExecutor.execute(() -> callback.onError(e));
+ callback.onError(e);
}
});
}
@@ -124,56 +121,58 @@
/**
* Searches joined app function static and runtime metadata using the function Id and the
* package.
- *
- * @hide
*/
private static @NonNull SearchResults searchJoinedStaticWithRuntimeAppFunctions(
@NonNull GlobalSearchSession session,
@NonNull String targetPackage,
@NonNull String functionIdentifier) {
SearchSpec runtimeSearchSpec =
- getAppFunctionRuntimeMetadataSearchSpecByFunctionId(targetPackage);
+ getAppFunctionRuntimeMetadataSearchSpecByPackageName(targetPackage);
JoinSpec joinSpec =
new JoinSpec.Builder(PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID)
- .setNestedSearch(functionIdentifier, runtimeSearchSpec)
+ .setNestedSearch(
+ buildFilerRuntimeMetadataByFunctionIdQuery(functionIdentifier),
+ runtimeSearchSpec)
.build();
SearchSpec joinedStaticWithRuntimeSearchSpec =
new SearchSpec.Builder()
- .setJoinSpec(joinSpec)
.addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
.addFilterSchemas(
AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage(
targetPackage))
- .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setJoinSpec(joinSpec)
+ .setVerbatimSearchEnabled(true)
.build();
- return session.search(functionIdentifier, joinedStaticWithRuntimeSearchSpec);
+ return session.search(
+ buildFilerStaticMetadataByFunctionIdQuery(functionIdentifier),
+ joinedStaticWithRuntimeSearchSpec);
}
/**
- * Finds whether the function is enabled or not from the search results returned by {@link
- * #searchJoinedStaticWithRuntimeAppFunctions}.
+ * Returns whether the function is effectively enabled or not from the search results returned
+ * by {@link #searchJoinedStaticWithRuntimeAppFunctions}.
*
+ * @param joinedStaticRuntimeResults search results joining AppFunctionStaticMetadata
+ * and AppFunctionRuntimeMetadata.
* @throws IllegalArgumentException if the function is not found in the results
- * @hide
*/
- private static boolean getEnabledStateFromSearchResults(
+ private static boolean getEffectiveEnabledStateFromSearchResults(
@NonNull List<SearchResult> joinedStaticRuntimeResults) {
if (joinedStaticRuntimeResults.isEmpty()) {
- // Function not found.
throw new IllegalArgumentException("App function not found.");
} else {
List<SearchResult> runtimeMetadataResults =
joinedStaticRuntimeResults.getFirst().getJoinedResults();
- if (!runtimeMetadataResults.isEmpty()) {
- Boolean result =
- (Boolean)
- runtimeMetadataResults
- .getFirst()
- .getGenericDocument()
- .getProperty(PROPERTY_ENABLED);
- if (result != null) {
- return result;
- }
+ if (runtimeMetadataResults.isEmpty()) {
+ throw new IllegalArgumentException("App function not found.");
+ }
+ boolean[] enabled =
+ runtimeMetadataResults
+ .getFirst()
+ .getGenericDocument()
+ .getPropertyBooleanArray(PROPERTY_ENABLED);
+ if (enabled != null && enabled.length != 0) {
+ return enabled[0];
}
// Runtime metadata not found. Using the default value in the static metadata.
return joinedStaticRuntimeResults
@@ -186,36 +185,39 @@
/**
* Returns search spec that queries app function metadata for a specific package name by its
* function identifier.
- *
- * @hide
*/
- public static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByFunctionId(
+ private static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByPackageName(
@NonNull String targetPackage) {
return new SearchSpec.Builder()
.addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
.addFilterSchemas(
AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(targetPackage))
- .addFilterProperties(
- AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(targetPackage),
- List.of(AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID))
- .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setVerbatimSearchEnabled(true)
.build();
}
- /**
- * Converts a failed app search result codes into an exception.
- *
- * @hide
- */
- public static @NonNull Exception failedResultToException(
+ private static String buildFilerRuntimeMetadataByFunctionIdQuery(String functionIdentifier) {
+ return TextUtils.formatSimple("%s:\"%s\"",
+ AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID,
+ functionIdentifier);
+ }
+
+ private static String buildFilerStaticMetadataByFunctionIdQuery(String functionIdentifier) {
+ return TextUtils.formatSimple("%s:\"%s\"",
+ AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID,
+ functionIdentifier);
+ }
+
+ /** Converts a failed app search result codes into an exception. */
+ private static @NonNull Exception failedResultToException(
@NonNull AppSearchResult appSearchResult) {
return switch (appSearchResult.getResultCode()) {
- case AppSearchResult.RESULT_INVALID_ARGUMENT ->
- new IllegalArgumentException(appSearchResult.getErrorMessage());
- case AppSearchResult.RESULT_IO_ERROR ->
- new IOException(appSearchResult.getErrorMessage());
- case AppSearchResult.RESULT_SECURITY_ERROR ->
- new SecurityException(appSearchResult.getErrorMessage());
+ case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException(
+ appSearchResult.getErrorMessage());
+ case AppSearchResult.RESULT_IO_ERROR -> new IOException(
+ appSearchResult.getErrorMessage());
+ case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException(
+ appSearchResult.getErrorMessage());
default -> new IllegalStateException(appSearchResult.getErrorMessage());
};
}
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index 83b5aa0..8b7f326 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -204,11 +204,17 @@
packageName, functionId));
}
+ public Builder(AppFunctionRuntimeMetadata original) {
+ this(original.getPackageName(), original.getFunctionId());
+ setEnabled(original.getEnabled());
+ }
+
/**
* Sets an indicator specifying if the function is enabled or not. This would override the
* default enabled state in the static metadata ({@link
- * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to
- * null to clear the override.
+ * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to null
+ * to clear the override.
+ * TODO(369683073) Replace the tristate Boolean with IntDef EnabledState.
*/
@NonNull
public Builder setEnabled(@Nullable Boolean enabled) {
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index f6580e6..4ed0a1b 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -99,6 +99,9 @@
/** The operation was timed out. */
public static final int RESULT_TIMED_OUT = 5;
+ /** The caller tried to execute a disabled app function. */
+ public static final int RESULT_DISABLED = 6;
+
/** The result code of the app function execution. */
@ResultCode private final int mResultCode;
@@ -274,6 +277,7 @@
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
RESULT_TIMED_OUT,
+ RESULT_DISABLED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ResultCode {}
diff --git a/core/java/android/app/appfunctions/IAppFunctionEnabledCallback.aidl b/core/java/android/app/appfunctions/IAppFunctionEnabledCallback.aidl
new file mode 100644
index 0000000..ced4155
--- /dev/null
+++ b/core/java/android/app/appfunctions/IAppFunctionEnabledCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import android.os.ParcelableException;
+
+/**
+ * @hide
+ */
+oneway interface IAppFunctionEnabledCallback {
+ void onSuccess();
+ void onError(in ParcelableException exception);
+}
diff --git a/core/java/android/app/appfunctions/IAppFunctionManager.aidl b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
index c63217f..72335e4 100644
--- a/core/java/android/app/appfunctions/IAppFunctionManager.aidl
+++ b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
@@ -17,9 +17,11 @@
package android.app.appfunctions;
import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.IAppFunctionEnabledCallback;
import android.app.appfunctions.IExecuteAppFunctionCallback;
import android.os.ICancellationSignal;
+import android.os.UserHandle;
/**
* Defines the interface for apps to interact with the app function execution service
* {@code AppFunctionManagerService} running in the system server process.
@@ -37,4 +39,15 @@
in ExecuteAppFunctionAidlRequest request,
in IExecuteAppFunctionCallback callback
);
+
+ /**
+ * Sets an AppFunction's enabled state provided by {@link AppFunctionService} through the system.
+ */
+ void setAppFunctionEnabled(
+ in String callingPackage,
+ in String functionIdentifier,
+ in UserHandle userHandle,
+ int enabledState,
+ in IAppFunctionEnabledCallback callback
+ );
}
diff --git a/core/java/android/ranging/mock/RangingFrameworkInitializer.java b/core/java/android/ranging/mock/RangingFrameworkInitializer.java
new file mode 100644
index 0000000..540f519
--- /dev/null
+++ b/core/java/android/ranging/mock/RangingFrameworkInitializer.java
@@ -0,0 +1,34 @@
+/*
+ * 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 android.ranging;
+
+/**
+* Mock RangingFrameworkInitializer.
+*
+* @hide
+*/
+
+// TODO(b/331206299): Remove this after RANGING_STACK_ENABLED is ramped up to next.
+public final class RangingFrameworkInitializer {
+ private RangingFrameworkInitializer() {}
+ /**
+ * @hide
+ */
+ public static void registerServiceWrappers() {
+ // No-op.
+ }
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 66776ce..ac208b5 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2064,7 +2064,11 @@
if (mAttachInfo.mThreadedRenderer == null) return;
if (mAttachInfo.mThreadedRenderer.setForceDark(determineForceDarkType())) {
// TODO: Don't require regenerating all display lists to apply this setting
- invalidateWorld(mView);
+ if (forceInvertColor()) {
+ destroyAndInvalidate();
+ } else {
+ invalidateWorld(mView);
+ }
}
}
@@ -11911,15 +11915,23 @@
public void onHighTextContrastStateChanged(boolean enabled) {
ThreadedRenderer.setHighContrastText(enabled);
- // Destroy Displaylists so they can be recreated with high contrast recordings
- destroyHardwareResources();
-
- // Schedule redraw, which will rerecord + redraw all text
- invalidate();
+ destroyAndInvalidate();
}
}
/**
+ * Destroy Displaylists so they can be recreated with new recordings, in case you are changing
+ * the way things are rendered (e.g. high contrast, force dark), then invalidate to trigger a
+ * redraw.
+ */
+ private void destroyAndInvalidate() {
+ destroyHardwareResources();
+
+ // Schedule redraw, which will rerecord + redraw all text
+ invalidate();
+ }
+
+ /**
* This class is an interface this ViewAncestor provides to the
* AccessibilityManagerService to the latter can interact with
* the view hierarchy in this ViewAncestor.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 2f649c2..1e5c6d8 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -465,13 +465,6 @@
private static final long USE_ASYNC_SHOW_HIDE_METHOD = 352594277L; // This is a bug id.
/**
- * Version-gating is guarded by bug-fix flag.
- */
- private static final boolean ASYNC_SHOW_HIDE_METHOD_ENABLED =
- !Flags.compatchangeForZerojankproxy()
- || CompatChanges.isChangeEnabled(USE_ASYNC_SHOW_HIDE_METHOD);
-
- /**
* If {@code true}, avoid calling the
* {@link com.android.server.inputmethod.InputMethodManagerService InputMethodManagerService}
* by skipping the call to {@link IInputMethodManager#startInputOrWindowGainedFocus}
@@ -614,6 +607,15 @@
@UnsupportedAppUsage
Rect mCursorRect = new Rect();
+ /**
+ * Version-gating is guarded by bug-fix flag.
+ */
+ // Note: this is non-static so that it only gets initialized once CompatChanges has
+ // access to the correct application context.
+ private final boolean mAsyncShowHideMethodEnabled =
+ !Flags.compatchangeForZerojankproxy()
+ || CompatChanges.isChangeEnabled(USE_ASYNC_SHOW_HIDE_METHOD);
+
/** Cached value for {@link #isStylusHandwritingAvailable} for userId. */
@GuardedBy("mH")
private PropertyInvalidatedCache<Integer, Boolean> mStylusHandwritingAvailableCache;
@@ -2419,7 +2421,7 @@
mCurRootView.getLastClickToolType(),
resultReceiver,
reason,
- ASYNC_SHOW_HIDE_METHOD_ENABLED);
+ mAsyncShowHideMethodEnabled);
}
}
}
@@ -2463,7 +2465,7 @@
mCurRootView.getLastClickToolType(),
resultReceiver,
reason,
- ASYNC_SHOW_HIDE_METHOD_ENABLED);
+ mAsyncShowHideMethodEnabled);
}
}
@@ -2572,7 +2574,7 @@
return true;
} else {
return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken,
- statsToken, flags, resultReceiver, reason, ASYNC_SHOW_HIDE_METHOD_ENABLED);
+ statsToken, flags, resultReceiver, reason, mAsyncShowHideMethodEnabled);
}
}
}
@@ -2615,7 +2617,7 @@
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, view.getWindowToken(),
- statsToken, flags, null, reason, ASYNC_SHOW_HIDE_METHOD_ENABLED);
+ statsToken, flags, null, reason, mAsyncShowHideMethodEnabled);
}
}
@@ -3392,7 +3394,7 @@
servedInputConnection == null ? null
: servedInputConnection.asIRemoteAccessibilityInputConnection(),
view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
- mImeDispatcher, ASYNC_SHOW_HIDE_METHOD_ENABLED);
+ mImeDispatcher, mAsyncShowHideMethodEnabled);
} else {
res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, startInputFlags,
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index b0e38e2..cff42fb 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -399,13 +399,10 @@
return -1;
}
case "enable-text" -> {
- if (mViewerConfigReader != null) {
- mViewerConfigReader.loadViewerConfig(groups, logger);
- }
- return setTextLogging(true, logger, groups);
+ return startLoggingToLogcat(groups, logger);
}
case "disable-text" -> {
- return setTextLogging(false, logger, groups);
+ return stopLoggingToLogcat(groups, logger);
}
default -> {
return unknownCommand(pw);
diff --git a/core/java/com/android/internal/protolog/Utils.java b/core/java/com/android/internal/protolog/Utils.java
index 1e6ba30..00ef80a 100644
--- a/core/java/com/android/internal/protolog/Utils.java
+++ b/core/java/com/android/internal/protolog/Utils.java
@@ -93,8 +93,7 @@
os.write(TAG, tag);
break;
default:
- throw new RuntimeException(
- "Unexpected field id " + pis.getFieldNumber());
+ Log.e(LOG_TAG, "Unexpected field id " + pis.getFieldNumber());
}
}
@@ -126,8 +125,7 @@
os.write(LOCATION, pis.readString(LOCATION));
break;
default:
- throw new RuntimeException(
- "Unexpected field id " + pis.getFieldNumber());
+ Log.e(LOG_TAG, "Unexpected field id " + pis.getFieldNumber());
}
}
diff --git a/graphics/java/android/graphics/PathIterator.java b/graphics/java/android/graphics/PathIterator.java
index 48b29f4..d7caabf 100644
--- a/graphics/java/android/graphics/PathIterator.java
+++ b/graphics/java/android/graphics/PathIterator.java
@@ -44,6 +44,8 @@
private final Path mPath;
private final int mPathGenerationId;
private static final int POINT_ARRAY_SIZE = 8;
+ private static final boolean IS_DALVIK = "dalvik".equalsIgnoreCase(
+ System.getProperty("java.vm.name"));
private static final NativeAllocationRegistry sRegistry =
NativeAllocationRegistry.createMalloced(
@@ -80,9 +82,14 @@
mPath = path;
mNativeIterator = nCreate(mPath.mNativePath);
mPathGenerationId = mPath.getGenerationId();
- final VMRuntime runtime = VMRuntime.getRuntime();
- mPointsArray = (float[]) runtime.newNonMovableArray(float.class, POINT_ARRAY_SIZE);
- mPointsAddress = runtime.addressOf(mPointsArray);
+ if (IS_DALVIK) {
+ final VMRuntime runtime = VMRuntime.getRuntime();
+ mPointsArray = (float[]) runtime.newNonMovableArray(float.class, POINT_ARRAY_SIZE);
+ mPointsAddress = runtime.addressOf(mPointsArray);
+ } else {
+ mPointsArray = new float[POINT_ARRAY_SIZE];
+ mPointsAddress = 0;
+ }
sRegistry.registerNativeAllocation(this, mNativeIterator);
}
@@ -177,7 +184,8 @@
throw new ConcurrentModificationException(
"Iterator cannot be used on modified Path");
}
- @Verb int verb = nNext(mNativeIterator, mPointsAddress);
+ @Verb int verb = IS_DALVIK
+ ? nNext(mNativeIterator, mPointsAddress) : nNextHost(mNativeIterator, mPointsArray);
if (verb == VERB_DONE) {
mDone = true;
}
@@ -287,6 +295,9 @@
private static native long nCreate(long nativePath);
private static native long nGetFinalizer();
+ /* nNextHost should be used for host runtimes, e.g. LayoutLib */
+ private static native int nNextHost(long nativeIterator, float[] points);
+
// ------------------ Critical JNI ------------------------
@CriticalNative
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
index bfccb29..e3a1d8a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
@@ -142,6 +142,19 @@
}
}
+ void abortTaskContainerRebuilding(@NonNull WindowContainerTransaction wct) {
+ // Clean-up the legacy states in the system
+ for (int i = mTaskFragmentInfos.size() - 1; i >= 0; i--) {
+ final TaskFragmentInfo info = mTaskFragmentInfos.valueAt(i);
+ mPresenter.deleteTaskFragment(wct, info.getFragmentToken());
+ }
+ mPresenter.setSavedState(new Bundle());
+
+ mParcelableTaskContainerDataList.clear();
+ mTaskFragmentInfos.clear();
+ mTaskFragmentParentInfos.clear();
+ }
+
boolean hasPendingStateToRestore() {
return !mParcelableTaskContainerDataList.isEmpty();
}
@@ -196,6 +209,7 @@
mController.onTaskFragmentParentRestored(wct, taskContainer.getTaskId(),
mTaskFragmentParentInfos.get(taskContainer.getTaskId()));
+ mTaskFragmentParentInfos.remove(taskContainer.getTaskId());
restoredAny = true;
}
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 db4bb0e..8345b40 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -56,6 +56,7 @@
import android.annotation.CallbackExecutor;
import android.app.Activity;
import android.app.ActivityClient;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.AppGlobals;
@@ -280,7 +281,7 @@
mSplitRules.clear();
mSplitRules.addAll(rules);
- if (!Flags.aeBackStackRestore() || !mPresenter.isRebuildTaskContainersNeeded()) {
+ if (!Flags.aeBackStackRestore() || !mPresenter.isWaitingToRebuildTaskContainers()) {
return;
}
@@ -2893,6 +2894,36 @@
return;
}
synchronized (mLock) {
+ if (mPresenter.isWaitingToRebuildTaskContainers()) {
+ Log.w(TAG, "Rebuilding aborted, clean up and restart");
+
+ // Retrieve the Task intent.
+ final int taskId = getTaskId(activity);
+ Intent taskIntent = null;
+ final ActivityManager am = activity.getSystemService(ActivityManager.class);
+ final List<ActivityManager.AppTask> appTasks = am.getAppTasks();
+ for (ActivityManager.AppTask appTask : appTasks) {
+ if (appTask.getTaskInfo().taskId == taskId) {
+ taskIntent = appTask.getTaskInfo().baseIntent.cloneFilter();
+ break;
+ }
+ }
+
+ // Clean up and abort the restoration
+ // TODO(b/369488857): also to remove the non-organized activities in the Task?
+ final TransactionRecord transactionRecord =
+ mTransactionManager.startNewTransaction();
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
+ mPresenter.abortTaskContainerRebuilding(wct);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+
+ // Start the Task root activity.
+ if (taskIntent != null) {
+ activity.startActivity(taskIntent);
+ }
+ return;
+ }
+
final IBinder activityToken = activity.getActivityToken();
final IBinder initialTaskFragmentToken =
getTaskFragmentTokenFromActivityClientRecord(activity);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 0c0ded9..b498ee2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -187,10 +187,14 @@
mBackupHelper.scheduleBackup();
}
- boolean isRebuildTaskContainersNeeded() {
+ boolean isWaitingToRebuildTaskContainers() {
return mBackupHelper.hasPendingStateToRestore();
}
+ void abortTaskContainerRebuilding(@NonNull WindowContainerTransaction wct) {
+ mBackupHelper.abortTaskContainerRebuilding(wct);
+ }
+
boolean rebuildTaskContainers(@NonNull WindowContainerTransaction wct,
@NonNull Set<EmbeddingRule> rules) {
return mBackupHelper.rebuildTaskContainers(wct, rules);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index dcc2d93..b453f1d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -156,7 +156,7 @@
mSplitController = splitController;
for (ParcelableTaskFragmentContainerData tfData :
data.getParcelableTaskFragmentContainerDataList()) {
- final TaskFragmentInfo info = taskFragmentInfoMap.get(tfData.mToken);
+ final TaskFragmentInfo info = taskFragmentInfoMap.remove(tfData.mToken);
if (info != null && !info.isEmpty()) {
final TaskFragmentContainer container =
new TaskFragmentContainer(tfData, splitController, this);
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 63a2880..cf0a975 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -156,6 +156,13 @@
}
flag {
+ name: "enable_flexible_two_app_split"
+ namespace: "multitasking"
+ description: "Enables only 2 app 90:10 split"
+ bug: "349828130"
+}
+
+flag {
name: "enable_flexible_split"
namespace: "multitasking"
description: "Enables flexibile split feature for split screen"
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/OWNERS
new file mode 100644
index 0000000..bfb6d4a
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/OWNERS
@@ -0,0 +1,4 @@
+jeremysim@google.com
+winsonc@google.com
+peanutbutter@google.com
+shuminghao@google.com
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
index 498dc8b..7f1e4a8 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
@@ -66,14 +66,54 @@
public @interface SplitPosition {
}
- /** A snap target in the first half of the screen, where the split is roughly 30-70. */
- public static final int SNAP_TO_30_70 = 0;
+ /**
+ * A snap target for two apps, where the split is 33-66. With FLAG_ENABLE_FLEXIBLE_SPLIT,
+ * only used on tablets.
+ */
+ public static final int SNAP_TO_2_33_66 = 0;
- /** The 50-50 snap target */
- public static final int SNAP_TO_50_50 = 1;
+ /** A snap target for two apps, where the split is 50-50. */
+ public static final int SNAP_TO_2_50_50 = 1;
- /** A snap target in the latter half of the screen, where the split is roughly 70-30. */
- public static final int SNAP_TO_70_30 = 2;
+ /**
+ * A snap target for two apps, where the split is 66-33. With FLAG_ENABLE_FLEXIBLE_SPLIT,
+ * only used on tablets.
+ */
+ public static final int SNAP_TO_2_66_33 = 2;
+
+ /**
+ * A snap target for two apps, where the split is 90-10. The "10" app extends off the screen,
+ * and is actually the same size as the onscreen app, but the visible portion takes up 10% of
+ * the screen. With FLAG_ENABLE_FLEXIBLE_SPLIT, used on phones and foldables.
+ */
+ public static final int SNAP_TO_2_90_10 = 3;
+
+ /**
+ * A snap target for two apps, where the split is 10-90. The "10" app extends off the screen,
+ * and is actually the same size as the onscreen app, but the visible portion takes up 10% of
+ * the screen. With FLAG_ENABLE_FLEXIBLE_SPLIT, used on phones and foldables.
+ */
+ public static final int SNAP_TO_2_10_90 = 4;
+
+ /**
+ * A snap target for three apps, where the split is 33-33-33. With FLAG_ENABLE_FLEXIBLE_SPLIT,
+ * only used on tablets.
+ */
+ public static final int SNAP_TO_3_33_33_33 = 5;
+
+ /**
+ * A snap target for three apps, where the split is 45-45-10. The "10" app extends off the
+ * screen, and is actually the same size as the onscreen apps, but the visible portion takes
+ * up 10% of the screen. With FLAG_ENABLE_FLEXIBLE_SPLIT, only used on unfolded foldables.
+ */
+ public static final int SNAP_TO_3_45_45_10 = 6;
+
+ /**
+ * A snap target for three apps, where the split is 10-45-45. The "10" app extends off the
+ * screen, and is actually the same size as the onscreen apps, but the visible portion takes
+ * up 10% of the screen. With FLAG_ENABLE_FLEXIBLE_SPLIT, only used on unfolded foldables.
+ */
+ public static final int SNAP_TO_3_10_45_45 = 7;
/**
* These snap targets are used for split pairs in a stable, non-transient state. They may be
@@ -81,9 +121,14 @@
* {@link SnapPosition}.
*/
@IntDef(prefix = { "SNAP_TO_" }, value = {
- SNAP_TO_30_70,
- SNAP_TO_50_50,
- SNAP_TO_70_30
+ SNAP_TO_2_33_66,
+ SNAP_TO_2_50_50,
+ SNAP_TO_2_66_33,
+ SNAP_TO_2_90_10,
+ SNAP_TO_2_10_90,
+ SNAP_TO_3_33_33_33,
+ SNAP_TO_3_45_45_10,
+ SNAP_TO_3_10_45_45,
})
public @interface PersistentSnapPosition {}
@@ -91,9 +136,14 @@
* Checks if the snapPosition in question is a {@link PersistentSnapPosition}.
*/
public static boolean isPersistentSnapPosition(@SnapPosition int snapPosition) {
- return snapPosition == SNAP_TO_30_70
- || snapPosition == SNAP_TO_50_50
- || snapPosition == SNAP_TO_70_30;
+ return snapPosition == SNAP_TO_2_33_66
+ || snapPosition == SNAP_TO_2_50_50
+ || snapPosition == SNAP_TO_2_66_33
+ || snapPosition == SNAP_TO_2_90_10
+ || snapPosition == SNAP_TO_2_10_90
+ || snapPosition == SNAP_TO_3_33_33_33
+ || snapPosition == SNAP_TO_3_45_45_10
+ || snapPosition == SNAP_TO_3_10_45_45;
}
/** The divider doesn't snap to any target and is freely placeable. */
@@ -109,9 +159,14 @@
public static final int SNAP_TO_MINIMIZE = 13;
@IntDef(prefix = { "SNAP_TO_" }, value = {
- SNAP_TO_30_70,
- SNAP_TO_50_50,
- SNAP_TO_70_30,
+ SNAP_TO_2_33_66,
+ SNAP_TO_2_50_50,
+ SNAP_TO_2_66_33,
+ SNAP_TO_2_90_10,
+ SNAP_TO_2_10_90,
+ SNAP_TO_3_33_33_33,
+ SNAP_TO_3_45_45_10,
+ SNAP_TO_3_10_45_45,
SNAP_TO_NONE,
SNAP_TO_START_AND_DISMISS,
SNAP_TO_END_AND_DISMISS,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index f7f45ae..9f100fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -19,9 +19,9 @@
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_30_70;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_70_30;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_MINIMIZE;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE;
@@ -283,10 +283,10 @@
private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition,
int bottomPosition, int dividerMax) {
- maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_30_70);
+ maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_2_33_66);
addMiddleTarget(isHorizontalDivision);
maybeAddTarget(bottomPosition,
- dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_70_30);
+ dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_2_66_33);
}
private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) {
@@ -332,7 +332,7 @@
private void addMiddleTarget(boolean isHorizontalDivision) {
int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
mInsets, mDisplayWidth, mDisplayHeight, mDividerSize);
- mTargets.add(new SnapTarget(position, SNAP_TO_50_50));
+ mTargets.add(new SnapTarget(position, SNAP_TO_2_50_50));
}
private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index d3bed59..a2439a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -361,8 +361,11 @@
final int anim = getRotationAnimationHint(change, info, mDisplayController);
isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
- startRotationAnimation(startTransaction, change, info, anim, animations,
- onAnimFinish);
+ final int flags = wallpaperTransit != WALLPAPER_TRANSITION_NONE
+ && Flags.commonSurfaceAnimator()
+ ? ScreenRotationAnimation.FLAG_HAS_WALLPAPER : 0;
+ startRotationAnimation(startTransaction, change, info, anim, flags,
+ animations, onAnimFinish);
isDisplayRotationAnimationStarted = true;
continue;
}
@@ -414,7 +417,7 @@
if (change.getParent() == null && !change.hasFlags(FLAG_IS_DISPLAY)
&& change.getStartRotation() != change.getEndRotation()) {
startRotationAnimation(startTransaction, change, info,
- ROTATION_ANIMATION_ROTATE, animations, onAnimFinish);
+ ROTATION_ANIMATION_ROTATE, 0 /* flags */, animations, onAnimFinish);
continue;
}
}
@@ -699,12 +702,12 @@
}
private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
- TransitionInfo.Change change, TransitionInfo info, int animHint,
+ TransitionInfo.Change change, TransitionInfo info, int animHint, int flags,
ArrayList<Animator> animations, Runnable onAnimFinish) {
final int rootIdx = TransitionUtil.rootIndexFor(change, info);
final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext,
mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(),
- animHint);
+ animHint, flags);
// The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
// content, and background color. The item of "animGroup" will be removed if the sub
// animation is finished. Then if the list becomes empty, the rotation animation is done.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 5802e2c..1a04997 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -25,12 +25,9 @@
import static com.android.wm.shell.transition.Transitions.TAG;
import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
-import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
@@ -38,6 +35,7 @@
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.window.ScreenCapture;
@@ -74,6 +72,7 @@
*/
class ScreenRotationAnimation {
static final int MAX_ANIMATION_DURATION = 10 * 1000;
+ static final int FLAG_HAS_WALLPAPER = 1;
private final Context mContext;
private final TransactionPool mTransactionPool;
@@ -98,6 +97,12 @@
private SurfaceControl mBackColorSurface;
/** The leash using to animate screenshot layer. */
private final SurfaceControl mAnimLeash;
+ /**
+ * The container with background color for {@link #mSurfaceControl}. It is only created if
+ * {@link #mSurfaceControl} may be translucent. E.g. visible wallpaper with alpha < 1 (dimmed).
+ * That prevents flickering of alpha blending.
+ */
+ private SurfaceControl mBackEffectSurface;
// The current active animation to move from the old to the new rotated
// state. Which animation is run here will depend on the old and new
@@ -111,8 +116,8 @@
/** Intensity of light/whiteness of the layout after rotation occurs. */
private float mEndLuma;
- ScreenRotationAnimation(Context context, TransactionPool pool,
- Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint) {
+ ScreenRotationAnimation(Context context, TransactionPool pool, Transaction t,
+ TransitionInfo.Change change, SurfaceControl rootLeash, int animHint, int flags) {
mContext = context;
mTransactionPool = pool;
mAnimHint = animHint;
@@ -170,11 +175,20 @@
}
hardwareBuffer.close();
}
+ if ((flags & FLAG_HAS_WALLPAPER) != 0) {
+ mBackEffectSurface = new SurfaceControl.Builder()
+ .setCallsite("ShellRotationAnimation").setParent(rootLeash)
+ .setEffectLayer().setOpaque(true).setName("BackEffect").build();
+ t.reparent(mSurfaceControl, mBackEffectSurface)
+ .setColor(mBackEffectSurface,
+ new float[] {mStartLuma, mStartLuma, mStartLuma})
+ .show(mBackEffectSurface);
+ }
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
t.show(mAnimLeash);
// Crop the real content in case it contains a larger child layer, e.g. wallpaper.
- t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
+ t.setCrop(getEnterSurface(), new Rect(0, 0, mEndWidth, mEndHeight));
if (!isCustomRotate()) {
mBackColorSurface = new SurfaceControl.Builder()
@@ -202,6 +216,11 @@
return mAnimHint == ROTATION_ANIMATION_CROSSFADE || mAnimHint == ROTATION_ANIMATION_JUMPCUT;
}
+ /** Returns the surface which contains the real content to animate enter. */
+ private SurfaceControl getEnterSurface() {
+ return mBackEffectSurface != null ? mBackEffectSurface : mSurfaceControl;
+ }
+
private void setScreenshotTransform(SurfaceControl.Transaction t) {
if (mScreenshotLayer == null) {
return;
@@ -314,7 +333,11 @@
} else {
startDisplayRotation(animations, finishCallback, mainExecutor);
startScreenshotRotationAnimation(animations, finishCallback, mainExecutor);
- //startColorAnimation(mTransaction, animationScale);
+ if (mBackEffectSurface != null && mStartLuma > 0.1f) {
+ // Animate from the color of background to black for smooth alpha blending.
+ buildLumaAnimation(animations, mStartLuma, 0f /* endLuma */, mBackEffectSurface,
+ animationScale, finishCallback, mainExecutor);
+ }
}
return true;
@@ -322,7 +345,7 @@
private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
- buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
+ buildSurfaceAnimation(animations, mRotateEnterAnimation, getEnterSurface(), finishCallback,
mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
null /* clipRect */, false /* isActivity */);
}
@@ -341,40 +364,17 @@
null /* clipRect */, false /* isActivity */);
}
- private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
- int colorTransitionMs = mContext.getResources().getInteger(
- R.integer.config_screen_rotation_color_transition);
- final float[] rgbTmpFloat = new float[3];
- final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
- final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
- final long duration = colorTransitionMs * (long) animationScale;
- final Transaction t = mTransactionPool.acquire();
-
- final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
- // Animation length is already expected to be scaled.
- va.overrideDurationScale(1.0f);
- va.setDuration(duration);
- va.addUpdateListener(animation -> {
- final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
- final float fraction = currentPlayTime / va.getDuration();
- applyColor(startColor, endColor, rgbTmpFloat, fraction, mBackColorSurface, t);
- });
- va.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
- t);
- mTransactionPool.release(t);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
- t);
- mTransactionPool.release(t);
- }
- });
- animExecutor.execute(va::start);
+ private void buildLumaAnimation(@NonNull ArrayList<Animator> animations,
+ float startLuma, float endLuma, SurfaceControl surface, float animationScale,
+ @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
+ final long durationMillis = (long) (mContext.getResources().getInteger(
+ R.integer.config_screen_rotation_color_transition) * animationScale);
+ final LumaAnimation animation = new LumaAnimation(durationMillis);
+ // Align the end with the enter animation.
+ animation.setStartOffset(mRotateEnterAnimation.getDuration() - durationMillis);
+ final LumaAnimationAdapter adapter = new LumaAnimationAdapter(surface, startLuma, endLuma);
+ DefaultSurfaceAnimator.buildSurfaceAnimation(animations, animation, finishCallback,
+ mTransactionPool, mainExecutor, adapter);
}
public void kill() {
@@ -389,21 +389,47 @@
if (mBackColorSurface != null && mBackColorSurface.isValid()) {
t.remove(mBackColorSurface);
}
+ if (mBackEffectSurface != null && mBackEffectSurface.isValid()) {
+ t.remove(mBackEffectSurface);
+ }
t.apply();
mTransactionPool.release(t);
}
- private static void applyColor(int startColor, int endColor, float[] rgbFloat,
- float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
- final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,
- endColor);
- Color middleColor = Color.valueOf(color);
- rgbFloat[0] = middleColor.red();
- rgbFloat[1] = middleColor.green();
- rgbFloat[2] = middleColor.blue();
- if (surface.isValid()) {
- t.setColor(surface, rgbFloat);
+ /** A no-op wrapper to provide animation duration. */
+ private static class LumaAnimation extends Animation {
+ LumaAnimation(long durationMillis) {
+ setDuration(durationMillis);
}
- t.apply();
+ }
+
+ private static class LumaAnimationAdapter extends DefaultSurfaceAnimator.AnimationAdapter {
+ final float[] mColorArray = new float[3];
+ final float mStartLuma;
+ final float mEndLuma;
+ final AccelerateInterpolator mInterpolation;
+
+ LumaAnimationAdapter(@NonNull SurfaceControl leash, float startLuma, float endLuma) {
+ super(leash);
+ mStartLuma = startLuma;
+ mEndLuma = endLuma;
+ // Make the initial progress color lighter if the background is light. That avoids
+ // darker content when fading into the entering surface.
+ final float factor = Math.min(3f, (Math.max(0.5f, mStartLuma) - 0.5f) * 10);
+ Slog.d(TAG, "Luma=" + mStartLuma + " factor=" + factor);
+ mInterpolation = factor > 0.5f ? new AccelerateInterpolator(factor) : null;
+ }
+
+ @Override
+ void applyTransformation(ValueAnimator animator, long currentPlayTime) {
+ final float fraction = mInterpolation != null
+ ? mInterpolation.getInterpolation(animator.getAnimatedFraction())
+ : animator.getAnimatedFraction();
+ final float luma = mStartLuma + fraction * (mEndLuma - mStartLuma);
+ mColorArray[0] = luma;
+ mColorArray[1] = luma;
+ mColorArray[2] = luma;
+ mTransaction.setColor(mLeash, mColorArray);
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
index 40dbbac..c8df15d 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
index 85715db..706c632 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
index 6c903a2..7df1675 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
index 6c903a2..7df1675 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
index f69a90c..d87c179 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index b76d065..99969e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
index 041978c..19c3e40 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
index bf040d2..7505860 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="on"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="on"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 177e47a..c52d9dd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -19,7 +19,7 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static com.google.common.truth.Truth.assertThat;
@@ -136,7 +136,7 @@
@Test
public void testSetDivideRatio() {
mSplitLayout.setDividerPosition(200, false /* applyLayoutChange */);
- mSplitLayout.setDivideRatio(SNAP_TO_50_50);
+ mSplitLayout.setDivideRatio(SNAP_TO_2_50_50);
assertThat(mSplitLayout.getDividerPosition()).isEqualTo(
mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
index 0c3f98a..0c100fc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
@@ -30,7 +30,7 @@
import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE
import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT
import com.android.wm.shell.shared.split.SplitBounds
-import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
@@ -136,7 +136,7 @@
assertThat(recentTaskInfoParcel.taskInfo2).isNotNull()
assertThat(recentTaskInfoParcel.taskInfo2!!.taskId).isEqualTo(2)
assertThat(recentTaskInfoParcel.splitBounds).isNotNull()
- assertThat(recentTaskInfoParcel.splitBounds!!.snapPosition).isEqualTo(SNAP_TO_50_50)
+ assertThat(recentTaskInfoParcel.splitBounds!!.snapPosition).isEqualTo(SNAP_TO_2_50_50)
}
@Test
@@ -185,7 +185,7 @@
private fun splitTasksGroupInfo(): GroupedRecentTaskInfo {
val task1 = createTaskInfo(id = 1)
val task2 = createTaskInfo(id = 2)
- val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_50_50)
+ val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50)
return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 386253c..753d4cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -22,7 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -211,10 +211,10 @@
// Verify only one update if the split info is the same
SplitBounds bounds1 = new SplitBounds(new Rect(0, 0, 50, 50),
- new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_50_50);
+ new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds1);
SplitBounds bounds2 = new SplitBounds(new Rect(0, 0, 50, 50),
- new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_50_50);
+ new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds2);
verify(mRecentTasksController, times(1)).notifyRecentTasksChanged();
}
@@ -246,9 +246,9 @@
// Mark a couple pairs [t2, t4], [t3, t5]
SplitBounds pair1Bounds =
- new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_2_50_50);
SplitBounds pair2Bounds =
- new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
@@ -277,9 +277,9 @@
// Mark a couple pairs [t2, t4], [t3, t5]
SplitBounds pair1Bounds =
- new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_2_50_50);
SplitBounds pair2Bounds =
- new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
@@ -339,7 +339,7 @@
setRawList(t1, t2, t3, t4, t5);
SplitBounds pair1Bounds =
- new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, pair1Bounds);
when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
@@ -449,7 +449,7 @@
// Add a pair
SplitBounds pair1Bounds =
- new SplitBounds(new Rect(), new Rect(), 2, 3, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 2, 3, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds);
reset(mRecentTasksController);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
index 248393c..be8e6dc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
@@ -1,6 +1,6 @@
package com.android.wm.shell.recents;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -46,21 +46,21 @@
@Test
public void testVerticalStacked() {
SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
- TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50);
assertTrue(ssb.appsStackedVertically);
}
@Test
public void testHorizontalStacked() {
SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
- TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50);
assertFalse(ssb.appsStackedVertically);
}
@Test
public void testHorizontalDividerBounds() {
SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
- TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50);
Rect dividerBounds = ssb.visualDividerBounds;
assertEquals(0, dividerBounds.left);
assertEquals(DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2, dividerBounds.top);
@@ -71,7 +71,7 @@
@Test
public void testVerticalDividerBounds() {
SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
- TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50);
Rect dividerBounds = ssb.visualDividerBounds;
assertEquals(DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, dividerBounds.left);
assertEquals(0, dividerBounds.top);
@@ -82,7 +82,7 @@
@Test
public void testEqualVerticalTaskPercent() {
SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
- TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50);
float topPercentSpaceTaken = (float) (DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2) / DEVICE_LENGTH;
assertEquals(topPercentSpaceTaken, ssb.topTaskPercent, 0.01);
}
@@ -90,7 +90,7 @@
@Test
public void testEqualHorizontalTaskPercent() {
SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
- TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50);
float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH;
assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt
index 19c18be..ac96063 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt
@@ -42,19 +42,44 @@
SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
)
assertEquals(
- "the value of SNAP_TO_30_70 should be 0",
+ "the value of SNAP_TO_2_33_66 should be 0",
0,
- SplitScreenConstants.SNAP_TO_30_70,
+ SplitScreenConstants.SNAP_TO_2_33_66,
)
assertEquals(
- "the value of SNAP_TO_50_50 should be 1",
+ "the value of SNAP_TO_2_50_50 should be 1",
1,
- SplitScreenConstants.SNAP_TO_50_50,
+ SplitScreenConstants.SNAP_TO_2_50_50,
)
assertEquals(
- "the value of SNAP_TO_70_30 should be 2",
+ "the value of SNAP_TO_2_66_33 should be 2",
2,
- SplitScreenConstants.SNAP_TO_70_30,
+ SplitScreenConstants.SNAP_TO_2_66_33,
+ )
+ assertEquals(
+ "the value of SNAP_TO_2_90_10 should be 3",
+ 3,
+ SplitScreenConstants.SNAP_TO_2_90_10,
+ )
+ assertEquals(
+ "the value of SNAP_TO_2_10_90 should be 4",
+ 4,
+ SplitScreenConstants.SNAP_TO_2_10_90,
+ )
+ assertEquals(
+ "the value of SNAP_TO_3_33_33_33 should be 5",
+ 5,
+ SplitScreenConstants.SNAP_TO_3_33_33_33,
+ )
+ assertEquals(
+ "the value of SNAP_TO_3_45_45_10 should be 6",
+ 6,
+ SplitScreenConstants.SNAP_TO_3_45_45_10,
+ )
+ assertEquals(
+ "the value of SNAP_TO_3_10_45_45 should be 7",
+ 7,
+ SplitScreenConstants.SNAP_TO_3_10_45_45,
)
}
}
\ No newline at end of file
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index 3ed33db..bb0fc41 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -5,6 +5,11 @@
ctor public AppFunctionManager(android.content.Context);
method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
method @Deprecated public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+ method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
+ field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
+ field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2
+ field public static final int APP_FUNCTION_STATE_ENABLED = 1; // 0x1
}
public abstract class AppFunctionService extends android.app.Service {
@@ -41,6 +46,7 @@
field public static final String PROPERTY_RETURN_VALUE = "returnValue";
field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
field public static final int RESULT_DENIED = 1; // 0x1
+ field public static final int RESULT_DISABLED = 6; // 0x6
field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
field public static final int RESULT_OK = 0; // 0x0
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
index 815fe05..d660926 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
@@ -16,11 +16,18 @@
package com.google.android.appfunctions.sidecar;
+import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserHandleAware;
import android.content.Context;
import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -37,6 +44,39 @@
// TODO(b/357551503): Implement get and set enabled app function APIs.
// TODO(b/367329899): Add sidecar library to Android B builds.
public final class AppFunctionManager {
+ /**
+ * The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset
+ * enabled state to the default value.
+ */
+ public static final int APP_FUNCTION_STATE_DEFAULT = 0;
+
+ /**
+ * The app function is enabled. To enable an app function, call {@link #setAppFunctionEnabled}
+ * with this value.
+ */
+ public static final int APP_FUNCTION_STATE_ENABLED = 1;
+
+ /**
+ * The app function is disabled. To disable an app function, call {@link #setAppFunctionEnabled}
+ * with this value.
+ */
+ public static final int APP_FUNCTION_STATE_DISABLED = 2;
+
+ /**
+ * The enabled state of the app function.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"APP_FUNCTION_STATE_"},
+ value = {
+ APP_FUNCTION_STATE_DEFAULT,
+ APP_FUNCTION_STATE_ENABLED,
+ APP_FUNCTION_STATE_DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EnabledState {}
+
private final android.app.appfunctions.AppFunctionManager mManager;
private final Context mContext;
@@ -111,4 +151,64 @@
new CancellationSignal(),
callback);
}
+
+ /**
+ * Returns a boolean through a callback, indicating whether the app function is enabled.
+ *
+ * <p>* This method can only check app functions owned by the caller, or those where the caller
+ * has visibility to the owner package and holds either the {@link
+ * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
+ * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
+ *
+ * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+ *
+ * <ul>
+ * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+ * have access to it.
+ * </ul>
+ *
+ * @param functionIdentifier the identifier of the app function to check (unique within the
+ * target package) and in most cases, these are automatically generated by the AppFunctions
+ * SDK
+ * @param targetPackage the package name of the app function's owner
+ * @param executor the executor to run the request
+ * @param callback the callback to receive the function enabled check result
+ */
+ public void isAppFunctionEnabled(
+ @NonNull String functionIdentifier,
+ @NonNull String targetPackage,
+ @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+ mManager.isAppFunctionEnabled(functionIdentifier, targetPackage, executor, callback);
+ }
+
+ /**
+ * Sets the enabled state of the app function owned by the calling package.
+ *
+ * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+ *
+ * <ul>
+ * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+ * have access to it.
+ * </ul>
+ *
+ * @param functionIdentifier the identifier of the app function to enable (unique within the
+ * calling package). In most cases, identifiers are automatically generated by the
+ * AppFunctions SDK
+ * @param newEnabledState the new state of the app function
+ * @param executor the executor to run the callback
+ * @param callback the callback to receive the result of the function enablement. The call was
+ * successful if no exception was thrown.
+ */
+ // Constants in @EnabledState should always mirror those in
+ // android.app.appfunctions.AppFunctionManager.
+ @SuppressLint("WrongConstant")
+ @UserHandleAware
+ public void setAppFunctionEnabled(
+ @NonNull String functionIdentifier,
+ @EnabledState int newEnabledState,
+ @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, Exception> callback) {
+ mManager.setAppFunctionEnabled(functionIdentifier, newEnabledState, executor, callback);
+ }
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
index 60c25fa..c7ce95b 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
@@ -76,6 +76,9 @@
/** The operation was timed out. */
public static final int RESULT_TIMED_OUT = 5;
+ /** The caller tried to execute a disabled app function. */
+ public static final int RESULT_DISABLED = 6;
+
/** The result code of the app function execution. */
@ResultCode private final int mResultCode;
@@ -234,6 +237,7 @@
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
RESULT_TIMED_OUT,
+ RESULT_DISABLED
})
@Retention(RetentionPolicy.SOURCE)
public @interface ResultCode {}
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index bbb1420..f0bcfe53 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -36,7 +36,7 @@
MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData,
size_t fontSize, std::string_view filePath, int ttcIndex,
- const std::vector<minikin::FontVariation>& axes)
+ const minikin::VariationSettings& axes)
: mTypeface(std::move(typeface))
, mSourceId(sourceId)
, mFontData(fontData)
@@ -123,12 +123,12 @@
return mTtcIndex;
}
-const std::vector<minikin::FontVariation>& MinikinFontSkia::GetAxes() const {
+const minikin::VariationSettings& MinikinFontSkia::GetAxes() const {
return mAxes;
}
std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation(
- const std::vector<minikin::FontVariation>& variations) const {
+ const minikin::VariationSettings& variations) const {
SkFontArguments args;
std::vector<SkFontArguments::VariationPosition::Coordinate> skVariation;
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
index de9a5c2..7fe5978bfd 100644
--- a/libs/hwui/hwui/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -32,7 +32,7 @@
public:
MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData, size_t fontSize,
std::string_view filePath, int ttcIndex,
- const std::vector<minikin::FontVariation>& axes);
+ const minikin::VariationSettings& axes);
float GetHorizontalAdvance(uint32_t glyph_id, const minikin::MinikinPaint& paint,
const minikin::FontFakery& fakery) const override;
@@ -59,9 +59,9 @@
size_t GetFontSize() const;
int GetFontIndex() const;
const std::string& getFilePath() const { return mFilePath; }
- const std::vector<minikin::FontVariation>& GetAxes() const;
+ const minikin::VariationSettings& GetAxes() const;
std::shared_ptr<minikin::MinikinFont> createFontWithVariation(
- const std::vector<minikin::FontVariation>&) const;
+ const minikin::VariationSettings&) const;
int GetSourceId() const override { return mSourceId; }
static uint32_t packFontFlags(const SkFont&);
@@ -80,7 +80,7 @@
const void* mFontData;
size_t mFontSize;
int mTtcIndex;
- std::vector<minikin::FontVariation> mAxes;
+ minikin::VariationSettings mAxes;
std::string mFilePath;
};
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index a9d1a2a..2d812d6 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -92,8 +92,8 @@
return result;
}
-Typeface* Typeface::createFromTypefaceWithVariation(
- Typeface* src, const std::vector<minikin::FontVariation>& variations) {
+Typeface* Typeface::createFromTypefaceWithVariation(Typeface* src,
+ const minikin::VariationSettings& variations) {
const Typeface* resolvedFace = Typeface::resolveDefault(src);
Typeface* result = new Typeface();
if (result != nullptr) {
@@ -192,9 +192,8 @@
sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(fontData));
LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
- std::shared_ptr<minikin::MinikinFont> font =
- std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, kRobotoFont,
- 0, std::vector<minikin::FontVariation>());
+ std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
+ std::move(typeface), 0, data, st.st_size, kRobotoFont, 0, minikin::VariationSettings());
std::vector<std::shared_ptr<minikin::Font>> fonts;
fonts.push_back(minikin::Font::Builder(font).build());
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index 565136e..2c96c1a 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -74,8 +74,8 @@
static Typeface* createRelative(Typeface* src, Style desiredStyle);
static Typeface* createAbsolute(Typeface* base, int weight, bool italic);
- static Typeface* createFromTypefaceWithVariation(
- Typeface* src, const std::vector<minikin::FontVariation>& variations);
+ static Typeface* createFromTypefaceWithVariation(Typeface* src,
+ const minikin::VariationSettings& variations);
static Typeface* createFromFamilies(
std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic,
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index e6d790f..9922ff3 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -133,9 +133,9 @@
builder->axes.clear();
return false;
}
- std::shared_ptr<minikin::MinikinFont> minikinFont =
- std::make_shared<MinikinFontSkia>(std::move(face), fonts::getNewSourceId(), fontPtr,
- fontSize, "", ttcIndex, builder->axes);
+ std::shared_ptr<minikin::MinikinFont> minikinFont = std::make_shared<MinikinFontSkia>(
+ std::move(face), fonts::getNewSourceId(), fontPtr, fontSize, "", ttcIndex,
+ minikin::VariationSettings(builder->axes, false));
minikin::Font::Builder fontBuilder(minikinFont);
if (weight != RESOLVE_BY_FONT_TABLE) {
diff --git a/libs/hwui/jni/PathIterator.cpp b/libs/hwui/jni/PathIterator.cpp
index 3884342..e9de655 100644
--- a/libs/hwui/jni/PathIterator.cpp
+++ b/libs/hwui/jni/PathIterator.cpp
@@ -20,6 +20,7 @@
#include "GraphicsJNI.h"
#include "SkPath.h"
#include "SkPoint.h"
+#include "graphics_jni_helpers.h"
namespace android {
@@ -36,6 +37,18 @@
return reinterpret_cast<jlong>(new SkPath::RawIter(*path));
}
+ // A variant of 'next' (below) that is compatible with the host JVM.
+ static jint nextHost(JNIEnv* env, jclass clazz, jlong iteratorHandle, jfloatArray pointsArray) {
+ jfloat* points = env->GetFloatArrayElements(pointsArray, 0);
+#ifdef __ANDROID__
+ jint result = next(iteratorHandle, reinterpret_cast<jlong>(points));
+#else
+ jint result = next(env, clazz, iteratorHandle, reinterpret_cast<jlong>(points));
+#endif
+ env->ReleaseFloatArrayElements(pointsArray, points, 0);
+ return result;
+ }
+
// ---------------- @CriticalNative -------------------------
static jint peek(CRITICAL_JNI_PARAMS_COMMA jlong iteratorHandle) {
@@ -72,6 +85,7 @@
{"nPeek", "(J)I", (void*)SkPathIteratorGlue::peek},
{"nNext", "(JJ)I", (void*)SkPathIteratorGlue::next},
+ {"nNextHost", "(J[F)I", (void*)SkPathIteratorGlue::nextHost},
};
int register_android_graphics_PathIterator(JNIEnv* env) {
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 209b35c..0f458dd 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -80,7 +80,8 @@
AxisHelper axis(env, axisObject);
variations.push_back(minikin::FontVariation(axis.getTag(), axis.getStyleValue()));
}
- return toJLong(Typeface::createFromTypefaceWithVariation(toTypeface(familyHandle), variations));
+ return toJLong(Typeface::createFromTypefaceWithVariation(
+ toTypeface(familyHandle), minikin::VariationSettings(variations, false /* sorted */)));
}
static jlong Typeface_createWeightAlias(JNIEnv* env, jobject, jlong familyHandle, jint weight) {
@@ -273,7 +274,7 @@
const std::string& path = typeface->GetFontPath();
writer->writeString(path);
writer->write<int>(typeface->GetFontIndex());
- const std::vector<minikin::FontVariation>& axes = typeface->GetAxes();
+ const minikin::VariationSettings& axes = typeface->GetAxes();
writer->writeArray<minikin::FontVariation>(axes.data(), axes.size());
bool hasVerity = getVerity(path);
writer->write<int8_t>(static_cast<int8_t>(hasVerity));
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index f405aba..6a05b6c 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -142,7 +142,7 @@
std::shared_ptr<minikin::MinikinFont> newMinikinFont = std::make_shared<MinikinFontSkia>(
std::move(newTypeface), minikinSkia->GetSourceId(), minikinSkia->GetFontData(),
minikinSkia->GetFontSize(), minikinSkia->getFilePath(), minikinSkia->GetFontIndex(),
- builder->axes);
+ minikin::VariationSettings(builder->axes, false));
std::shared_ptr<minikin::Font> newFont = minikin::Font::Builder(newMinikinFont)
.setWeight(weight)
.setSlant(static_cast<minikin::FontStyle::Slant>(italic))
@@ -303,7 +303,7 @@
var = reader.readArray<minikin::FontVariation>().first[index];
} else {
const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
- var = minikinFont->GetAxes().at(index);
+ var = minikinFont->GetAxes()[index];
}
uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 91f78ce..0c07b2a 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -327,7 +327,7 @@
result->mWeight = font->style().weight();
result->mItalic = font->style().slant() == minikin::FontStyle::Slant::ITALIC;
result->mCollectionIndex = minikinFontSkia->GetFontIndex();
- const std::vector<minikin::FontVariation>& axes = minikinFontSkia->GetAxes();
+ const minikin::VariationSettings& axes = minikinFontSkia->GetAxes();
result->mAxes.reserve(axes.size());
for (auto axis : axes) {
result->mAxes.push_back(std::make_pair(axis.axisTag, axis.value));
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index af07686..06f471e 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -42,6 +42,7 @@
"SettingsLibFooterPreference",
"SettingsLibHelpUtils",
"SettingsLibIllustrationPreference",
+ "SettingsLibIntroPreference",
"SettingsLibLayoutPreference",
"SettingsLibMainSwitchPreference",
"SettingsLibProfileSelector",
diff --git a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
index 47ce587..b06052a 100644
--- a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
@@ -78,15 +78,6 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
android:visibility="gone"/>
-
- <ProgressBar
- android:id="@android:id/progress"
- style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="match_parent"
- android:layout_height="4dp"
- android:layout_marginTop="4dp"
- android:max="100"
- android:visibility="gone"/>
</LinearLayout>
<LinearLayout
diff --git a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
index e65f7de..ac57228 100644
--- a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
@@ -74,15 +74,6 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
android:visibility="gone"/>
-
- <ProgressBar
- android:id="@android:id/progress"
- style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="4dp"
- android:max="100"
- android:visibility="gone"/>
</LinearLayout>
<LinearLayout
diff --git a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
index f1d162e..3b52df7 100644
--- a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
+++ b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
@@ -18,11 +18,8 @@
import android.content.Context;
import android.util.AttributeSet;
-import android.view.View;
-import android.widget.ProgressBar;
import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.widget.preference.app.R;
@@ -31,9 +28,6 @@
*/
public class AppPreference extends Preference {
- private int mProgress;
- private boolean mProgressVisible;
-
public AppPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setLayoutResource(R.layout.preference_app);
@@ -53,29 +47,4 @@
super(context, attrs);
setLayoutResource(R.layout.preference_app);
}
-
- /**
- * Sets the current progress.
- * @param amount the current progress
- *
- * @see ProgressBar#setProgress(int)
- */
- public void setProgress(int amount) {
- mProgress = amount;
- mProgressVisible = true;
- notifyChanged();
- }
-
- @Override
- public void onBindViewHolder(PreferenceViewHolder view) {
- super.onBindViewHolder(view);
-
- final ProgressBar progress = (ProgressBar) view.findViewById(android.R.id.progress);
- if (mProgressVisible) {
- progress.setProgress(mProgress);
- progress.setVisibility(View.VISIBLE);
- } else {
- progress.setVisibility(View.GONE);
- }
- }
}
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml
new file mode 100644
index 0000000..f55b320
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Filled" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml
new file mode 100644
index 0000000..b663b6c
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Filled.Extra" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml
new file mode 100644
index 0000000..784e6ad
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Filled.Large" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml
new file mode 100644
index 0000000..8b44a65
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Outline" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml
new file mode 100644
index 0000000..f8a2d8f
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Outline.Extra" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml
new file mode 100644
index 0000000..781a5a1
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Outline.Large" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml
new file mode 100644
index 0000000..5b568f8
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Tonal" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml
new file mode 100644
index 0000000..1e7a08b
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Tonal.Extra" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml
new file mode 100644
index 0000000..42116be
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Tonal.Large" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml b/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml
new file mode 100644
index 0000000..a1761e5
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <declare-styleable name="ButtonPreference">
+ <attr name="buttonType" format="enum">
+ <enum name="filled" value="0"/>
+ <enum name="tonal" value="1"/>
+ <enum name="outline" value="2"/>
+ </attr>
+ <attr name="buttonSize" format="enum">
+ <enum name="normal" value="0"/>
+ <enum name="large" value="1"/>
+ <enum name="extra" value="2"/>
+ </attr>
+ </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
index 16ba962..0041eb2 100644
--- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
@@ -32,11 +32,44 @@
import com.android.settingslib.widget.preference.button.R;
+import com.google.android.material.button.MaterialButton;
+
/**
* A preference handled a button
*/
public class ButtonPreference extends Preference {
+ enum ButtonStyle {
+ FILLED_NORMAL(0, 0, R.layout.settingslib_expressive_button_filled),
+ FILLED_LARGE(0, 1, R.layout.settingslib_expressive_button_filled_large),
+ FILLED_EXTRA(0, 2, R.layout.settingslib_expressive_button_filled_extra),
+ TONAL_NORMAL(1, 0, R.layout.settingslib_expressive_button_tonal),
+ TONAL_LARGE(1, 1, R.layout.settingslib_expressive_button_tonal_large),
+ TONAL_EXTRA(1, 2, R.layout.settingslib_expressive_button_tonal_extra),
+ OUTLINE_NORMAL(2, 0, R.layout.settingslib_expressive_button_outline),
+ OUTLINE_LARGE(2, 1, R.layout.settingslib_expressive_button_outline_large),
+ OUTLINE_EXTRA(2, 2, R.layout.settingslib_expressive_button_outline_extra);
+
+ private final int mType;
+ private final int mSize;
+ private final int mLayoutId;
+
+ ButtonStyle(int type, int size, int layoutId) {
+ this.mType = type;
+ this.mSize = size;
+ this.mLayoutId = layoutId;
+ }
+
+ static int getLayoutId(int type, int size) {
+ for (ButtonStyle style : values()) {
+ if (style.mType == type && style.mSize == size) {
+ return style.mLayoutId;
+ }
+ }
+ throw new IllegalArgumentException();
+ }
+ }
+
private static final int ICON_SIZE = 24;
private View.OnClickListener mClickListener;
@@ -86,7 +119,7 @@
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
- setLayoutResource(R.layout.settingslib_button_layout);
+ int resId = R.layout.settingslib_button_layout;
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs,
@@ -102,8 +135,16 @@
R.styleable.ButtonPreference, defStyleAttr,
0 /*defStyleRes*/);
mGravity = a.getInt(R.styleable.ButtonPreference_android_gravity, Gravity.START);
+
+ if (SettingsThemeHelper.isExpressiveTheme(context)) {
+ int type = a.getInt(R.styleable.ButtonPreference_buttonType, 0);
+ int size = a.getInt(R.styleable.ButtonPreference_buttonSize, 0);
+ resId = ButtonStyle.getLayoutId(type, size);
+ }
a.recycle();
}
+
+ setLayoutResource(resId);
}
@Override
@@ -144,14 +185,20 @@
if (mButton == null || icon == null) {
return;
}
- //get pixel from dp
- int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_SIZE,
- getContext().getResources().getDisplayMetrics());
- icon.setBounds(0, 0, size, size);
- //set drawableStart
- mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null/* top */, null/* end */,
- null/* bottom */);
+ if (mButton instanceof MaterialButton) {
+ ((MaterialButton) mButton).setIcon(icon);
+ } else {
+ //get pixel from dp
+ int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_SIZE,
+ getContext().getResources().getDisplayMetrics());
+ icon.setBounds(0, 0, size, size);
+
+ //set drawableStart
+ mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null/* top */,
+ null/* end */,
+ null/* bottom */);
+ }
}
@Override
diff --git a/packages/SettingsLib/IntroPreference/Android.bp b/packages/SettingsLib/IntroPreference/Android.bp
new file mode 100644
index 0000000..155db18
--- /dev/null
+++ b/packages/SettingsLib/IntroPreference/Android.bp
@@ -0,0 +1,33 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+ name: "SettingsLibIntroPreference",
+ use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "androidx.preference_preference",
+ "SettingsLibSettingsTheme",
+ ],
+
+ sdk_version: "system_current",
+ min_sdk_version: "21",
+ apex_available: [
+ "//apex_available:platform",
+ ],
+}
diff --git a/packages/SettingsLib/IntroPreference/AndroidManifest.xml b/packages/SettingsLib/IntroPreference/AndroidManifest.xml
new file mode 100644
index 0000000..f1bfee5
--- /dev/null
+++ b/packages/SettingsLib/IntroPreference/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.widget.preference.intro">
+
+ <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
new file mode 100644
index 0000000..203a395
--- /dev/null
+++ b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/entity_header"
+ style="@style/SettingsLibEntityHeader">
+
+ <LinearLayout
+ android:id="@+id/entity_header_content"
+ style="@style/SettingsLibEntityHeaderContent">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:src="@drawable/settingslib_arrow_drop_down"
+ style="@style/SettingsLibEntityHeaderIcon"/>
+
+ <TextView
+ android:id="@android:id/title"
+ android:text="Title"
+ style="@style/SettingsLibEntityHeaderTitle"/>
+
+ <com.android.settingslib.widget.CollapsableTextView
+ android:id="@+id/collapsable_summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"/>
+
+ </LinearLayout>
+
+</RelativeLayout>
diff --git a/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt b/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
new file mode 100644
index 0000000..c93ec2b
--- /dev/null
+++ b/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.settingslib.widget
+
+import android.content.Context
+import android.os.Build
+import android.util.AttributeSet
+import androidx.annotation.RequiresApi
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.android.settingslib.widget.preference.intro.R
+
+class IntroPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes) {
+
+ private var isCollapsable: Boolean = false
+ private var minLines: Int = 2
+
+ init {
+ layoutResource = R.layout.settingslib_expressive_preference_intro
+ isSelectable = false
+
+ initAttributes(context, attrs, defStyleAttr)
+ }
+
+ private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ context.obtainStyledAttributes(
+ attrs,
+ COLLAPSABLE_TEXT_VIEW_ATTRS, defStyleAttr, 0
+ ).apply {
+ isCollapsable = getBoolean(IS_COLLAPSABLE, false)
+ minLines = getInt(
+ MIN_LINES,
+ if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES
+ ).coerceIn(1, DEFAULT_MAX_LINES)
+ recycle()
+ }
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ holder.isDividerAllowedBelow = false
+ holder.isDividerAllowedAbove = false
+
+ (holder.findViewById(R.id.collapsable_summary) as? CollapsableTextView)?.apply {
+ setCollapsable(isCollapsable)
+ setMinLines(minLines)
+ setText(summary.toString())
+ }
+ }
+
+ /**
+ * Sets whether the summary is collapsable.
+ * @param collapsable True if the summary should be collapsable, false otherwise.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ fun setCollapsable(collapsable: Boolean) {
+ isCollapsable = collapsable
+ minLines = if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES
+ notifyChanged()
+ }
+
+ /**
+ * Sets the minimum number of lines to display when collapsed.
+ * @param lines The minimum number of lines.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ fun setMinLines(lines: Int) {
+ minLines = lines.coerceIn(1, DEFAULT_MAX_LINES)
+ notifyChanged()
+ }
+
+ companion object {
+ private const val DEFAULT_MAX_LINES = 10
+ private const val DEFAULT_MIN_LINES = 2
+
+ private val COLLAPSABLE_TEXT_VIEW_ATTRS =
+ com.android.settingslib.widget.theme.R.styleable.CollapsableTextView
+ private val MIN_LINES =
+ com.android.settingslib.widget.theme.R.styleable.CollapsableTextView_android_minLines
+ private val IS_COLLAPSABLE =
+ com.android.settingslib.widget.theme.R.styleable.CollapsableTextView_isCollapsable
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Preference/Android.bp b/packages/SettingsLib/Preference/Android.bp
index 17852e8..e83e17c 100644
--- a/packages/SettingsLib/Preference/Android.bp
+++ b/packages/SettingsLib/Preference/Android.bp
@@ -22,3 +22,16 @@
],
kotlincflags: ["-Xjvm-default=all"],
}
+
+android_library {
+ name: "SettingsLibPreference-testutils",
+ srcs: ["testutils/**/*.kt"],
+ static_libs: [
+ "SettingsLibPreference",
+ "androidx.fragment_fragment-testing",
+ "androidx.test.core",
+ "androidx.test.ext.junit",
+ "flag-junit",
+ "truth",
+ ],
+}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index 5fcf478..5e69895 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -68,10 +68,13 @@
preference.icon = null
}
val context = preference.context
+ val isPreferenceScreen = preference is PreferenceScreen
preference.peekExtras()?.clear()
extras(context)?.let { preference.extras.putAll(it) }
preference.title = getPreferenceTitle(context)
- preference.summary = getPreferenceSummary(context)
+ if (!isPreferenceScreen) {
+ preference.summary = getPreferenceSummary(context)
+ }
preference.isEnabled = isEnabled(context)
preference.isVisible =
(this as? PreferenceAvailabilityProvider)?.isAvailable(context) != false
@@ -81,7 +84,7 @@
// dependency here. This simplifies dependency management and avoid the
// IllegalStateException when call Preference.setDependency
preference.dependency = null
- if (preference !is PreferenceScreen) { // avoid recursive loop when build graph
+ if (!isPreferenceScreen) { // avoid recursive loop when build graph
preference.fragment = (this as? PreferenceScreenCreator)?.fragmentClass()?.name
preference.intent = intent(context)
}
diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt
new file mode 100644
index 0000000..4d5f85f
--- /dev/null
+++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.settingslib.preference
+
+import android.content.Context
+import android.platform.test.flag.junit.SetFlagsRule
+import android.util.Log
+import androidx.fragment.app.testing.FragmentScenario
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.PreferenceGroup
+import androidx.preference.PreferenceScreen
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Test case for catalyst screen. */
+@RunWith(AndroidJUnit4::class)
+abstract class CatalystScreenTestCase {
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ protected val context: Context = ApplicationProvider.getApplicationContext()
+
+ /** Catalyst screen. */
+ protected abstract val preferenceScreenCreator: PreferenceScreenCreator
+
+ /** Flag to control catalyst screen. */
+ protected abstract val flagName: String
+
+ /**
+ * Test to compare the preference screen hierarchy between legacy screen (flag is disabled) and
+ * catalyst screen (flag is enabled).
+ */
+ @Test
+ fun migration() {
+ enableCatalystScreen()
+ assertThat(preferenceScreenCreator.isFlagEnabled(context)).isTrue()
+ val catalystScreen = stringifyPreferenceScreen()
+ Log.i("Catalyst", catalystScreen)
+
+ disableCatalystScreen()
+ assertThat(preferenceScreenCreator.isFlagEnabled(context)).isFalse()
+ val legacyScreen = stringifyPreferenceScreen()
+
+ assertThat(catalystScreen).isEqualTo(legacyScreen)
+ }
+
+ /**
+ * Enables the catalyst screen.
+ *
+ * By default, enable the [flagName]. Override for more complex situation.
+ */
+ @Suppress("DEPRECATION")
+ protected open fun enableCatalystScreen() {
+ setFlagsRule.enableFlags(flagName)
+ }
+
+ /**
+ * Disables the catalyst screen (legacy screen is shown).
+ *
+ * By default, disable the [flagName]. Override for more complex situation.
+ */
+ @Suppress("DEPRECATION")
+ protected open fun disableCatalystScreen() {
+ setFlagsRule.disableFlags(flagName)
+ }
+
+ private fun stringifyPreferenceScreen(): String {
+ @Suppress("UNCHECKED_CAST")
+ val clazz = preferenceScreenCreator.fragmentClass() as Class<PreferenceFragmentCompat>
+ val builder = StringBuilder()
+ FragmentScenario.launch(clazz).use {
+ it.onFragment { fragment -> fragment.preferenceScreen.toString(builder) }
+ }
+ return builder.toString()
+ }
+
+ private fun Preference.toString(builder: StringBuilder, indent: String = "") {
+ val clazz = javaClass
+ builder.append(indent).append(clazz).append(" {\n")
+ val indent2 = "$indent "
+ if (clazz != PreferenceScreen::class.java) {
+ key?.let { builder.append(indent2).append("key: \"$it\"\n") }
+ }
+ title?.let { builder.append(indent2).append("title: \"$it\"\n") }
+ summary?.let { builder.append(indent2).append("summary: \"$it\"\n") }
+ fragment?.let { builder.append(indent2).append("fragment: \"$it\"\n") }
+ builder.append(indent2).append("order: $order\n")
+ builder.append(indent2).append("isCopyingEnabled: $isCopyingEnabled\n")
+ builder.append(indent2).append("isEnabled: $isEnabled\n")
+ builder.append(indent2).append("isIconSpaceReserved: $isIconSpaceReserved\n")
+ if (clazz != Preference::class.java && clazz != PreferenceScreen::class.java) {
+ builder.append(indent2).append("isPersistent: $isPersistent\n")
+ }
+ builder.append(indent2).append("isSelectable: $isSelectable\n")
+ if (this is PreferenceGroup) {
+ val count = preferenceCount
+ builder.append(indent2).append("preferenceCount: $count\n")
+ val indent4 = "$indent2 "
+ for (index in 0..<count) {
+ getPreference(index).toString(builder, indent4)
+ }
+ }
+ builder.append(indent).append("}\n")
+ }
+}
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
index 250c27e..9c65905 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
@@ -290,4 +290,43 @@
<item name="iconPadding">@dimen/settingslib_expressive_space_extrasmall4</item>
<item name="rippleColor">?android:attr/colorControlHighlight</item>
</style>
+
+ <style name="EntityHeader">
+ <item name="android:paddingTop">@dimen/settingslib_expressive_space_small4</item>
+ <item name="android:paddingBottom">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:paddingEnd">@dimen/settingslib_expressive_space_small1</item>
+ </style>
+
+ <style name="SettingsLibEntityHeader" parent="EntityHeader">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:paddingStart">?android:attr/listPreferredItemPaddingStart</item>
+ <item name="android:paddingEnd">?android:attr/listPreferredItemPaddingEnd</item>
+ </style>
+
+ <style name="SettingsLibEntityHeaderContent">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_centerHorizontal">true</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:gravity">center_horizontal</item>
+ </style>
+
+ <style name="SettingsLibEntityHeaderIcon">
+ <item name="android:layout_width">@dimen/settingslib_expressive_space_large3</item>
+ <item name="android:layout_height">@dimen/settingslib_expressive_space_large3</item>
+ <item name="android:scaleType">fitCenter</item>
+ <item name="android:antialias">true</item>
+ </style>
+
+ <style name="SettingsLibEntityHeaderTitle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:singleLine">false</item>
+ <item name="android:gravity">center</item>
+ <item name="android:ellipsize">marquee</item>
+ <item name="android:textDirection">locale</item>
+ <item name="android:textAppearance">@style/TextAppearance.EntityHeaderTitle</item>
+ </style>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 7139f5b4..2a251a5 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -30,9 +30,6 @@
import com.android.settingslib.spa.gallery.editor.SettingsDropdownBoxPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsDropdownCheckBoxProvider
import com.android.settingslib.spa.gallery.home.HomePageProvider
-import com.android.settingslib.spa.gallery.itemList.ItemListPageProvider
-import com.android.settingslib.spa.gallery.itemList.ItemOperatePageProvider
-import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsOutlinedTextFieldPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsTextFieldPasswordPageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
@@ -66,10 +63,6 @@
*/
enum class SettingsPageProviderEnum(val displayName: String) {
HOME("home"),
- PREFERENCE("preference"),
- ARGUMENT("argument"),
- ITEM_LIST("itemList"),
- ITEM_OP_PAGE("itemOp"),
// Add your SPPs
}
@@ -101,9 +94,6 @@
ChartPageProvider,
DialogMainPageProvider,
NavDialogProvider,
- ItemListPageProvider,
- ItemOperatePageProvider,
- OperateListPageProvider,
EditorMainPageProvider,
SettingsOutlinedTextFieldPageProvider,
SettingsDropdownBoxPageProvider,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/banner/BannerPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/banner/BannerPageProvider.kt
index 6edd917..c16d8bf 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/banner/BannerPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/banner/BannerPageProvider.kt
@@ -39,9 +39,7 @@
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -161,14 +159,12 @@
}
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = createSettingsPage())
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
private const val TITLE = "Sample Banner"
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
index b001cad..773d3d1 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
@@ -23,9 +23,7 @@
import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.button.ActionButton
@@ -55,14 +53,12 @@
}
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = createSettingsPage())
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt
index 7a6ae2c..6ceb395 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt
@@ -39,6 +39,7 @@
private enum class WeekDay(val num: Int) {
Sun(0), Mon(1), Tue(2), Wed(3), Thu(4), Fri(5), Sat(6),
}
+
private const val TITLE = "Sample Chart"
object ChartPageProvider : SettingsPageProvider {
@@ -103,14 +104,12 @@
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
index 4e3fcee..c9c81aa 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
@@ -18,6 +18,7 @@
import android.os.Bundle
import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -55,13 +56,13 @@
}.build(),
)
- fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
override fun getTitle(arguments: Bundle?) = TITLE
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
index c511542..f2b4091 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
@@ -17,8 +17,8 @@
package com.android.settingslib.spa.gallery.editor
import android.os.Bundle
+import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
@@ -44,14 +44,12 @@
)
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index b1558cc..4d77ea1 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -20,20 +20,16 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.banner.BannerPageProvider
+import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
-import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
-import com.android.settingslib.spa.gallery.page.ArgumentPageModel
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
@@ -48,35 +44,11 @@
import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
import com.android.settingslib.spa.widget.scaffold.HomeScaffold
+import com.android.settingslib.spa.widget.ui.Category
object HomePageProvider : SettingsPageProvider {
override val name = SettingsPageProviderEnum.HOME.name
override val displayName = SettingsPageProviderEnum.HOME.displayName
- private val owner = createSettingsPage()
-
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- return listOf(
- PreferenceMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- OperateListPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
- SearchScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- SuwScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- PagerMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- FooterPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- LoadingBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- DialogMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- EditorMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- BannerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- CopyablePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- )
- }
override fun getTitle(arguments: Bundle?): String {
return SpaEnvironmentFactory.instance.appContext.getString(R.string.app_name)
@@ -85,14 +57,30 @@
@Composable
override fun Page(arguments: Bundle?) {
val title = remember { getTitle(arguments) }
- val entries = remember { buildEntry(arguments) }
HomeScaffold(title) {
- for (entry in entries) {
- if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
- entry.UiLayout(ArgumentPageModel.buildArgument(intParam = 0))
- } else {
- entry.UiLayout()
- }
+ Category {
+ PreferenceMainPageProvider.Entry()
+ }
+ Category {
+ SearchScaffoldPageProvider.Entry()
+ SuwScaffoldPageProvider.Entry()
+ ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0)
+ }
+ Category {
+ SliderPageProvider.Entry()
+ SpinnerPageProvider.Entry()
+ PagerMainPageProvider.Entry()
+ FooterPageProvider.Entry()
+ IllustrationPageProvider.Entry()
+ CategoryPageProvider.Entry()
+ ActionButtonPageProvider.Entry()
+ ProgressBarPageProvider.Entry()
+ LoadingBarPageProvider.Entry()
+ ChartPageProvider.Entry()
+ DialogMainPageProvider.Entry()
+ EditorMainPageProvider.Entry()
+ BannerPageProvider.Entry()
+ CopyablePageProvider.Entry()
}
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt
deleted file mode 100644
index 5f251b1..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt
+++ /dev/null
@@ -1,99 +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.settingslib.spa.gallery.itemList
-
-import android.os.Bundle
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.core.os.bundleOf
-import androidx.navigation.NavType
-import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.util.getStringArg
-import com.android.settingslib.spa.framework.util.navLink
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
-
-private const val OPERATOR_PARAM_NAME = "opParam"
-
-object ItemListPageProvider : SettingsPageProvider {
- override val name = SettingsPageProviderEnum.ITEM_LIST.name
- override val displayName = SettingsPageProviderEnum.ITEM_LIST.displayName
- override val parameter = listOf(
- navArgument(OPERATOR_PARAM_NAME) { type = NavType.StringType },
- )
-
- override fun getTitle(arguments: Bundle?): String {
- val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments) ?: "NULL"
- return "Operation: $operation"
- }
-
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- if (!ItemOperatePageProvider.isValidArgs(arguments)) return emptyList()
- val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)!!
- val owner = createSettingsPage(arguments)
- return listOf(
- ItemOperatePageProvider.buildInjectEntry(operation)!!.setLink(fromPage = owner).build(),
- )
- }
-
- fun buildInjectEntry(opParam: String): SettingsEntryBuilder? {
- val arguments = bundleOf(OPERATOR_PARAM_NAME to opParam)
- if (!ItemOperatePageProvider.isValidArgs(arguments)) return null
-
- return SettingsEntryBuilder.createInject(
- owner = createSettingsPage(arguments),
- label = "ItemList_$opParam",
- ).setUiLayoutFn {
- Preference(
- object : PreferenceModel {
- override val title = opParam
- override val onClick = navigator(
- SettingsPageProviderEnum.ITEM_LIST.name + parameter.navLink(it)
- )
- }
- )
- }.setSearchDataFn {
- EntrySearchData(title = "Operation: $opParam")
- }
- }
-
- @Composable
- override fun Page(arguments: Bundle?) {
- val title = remember { getTitle(arguments) }
- val entries = remember { buildEntry(arguments) }
- val itemList = remember {
- // Add logic to get item List during runtime.
- listOf("itemFoo", "itemBar", "itemToy")
- }
- RegularScaffold(title) {
- for (item in itemList) {
- val rtArgs = ItemOperatePageProvider.genRuntimeArguments(item)
- for (entry in entries) {
- entry.UiLayout(rtArgs)
- }
- }
- }
- }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt
deleted file mode 100644
index 6caec07..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt
+++ /dev/null
@@ -1,127 +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.settingslib.spa.gallery.itemList
-
-import android.os.Bundle
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.core.os.bundleOf
-import androidx.navigation.NavType
-import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.util.getStringArg
-import com.android.settingslib.spa.framework.util.navLink
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.preference.SwitchPreference
-import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-
-private const val OPERATOR_PARAM_NAME = "opParam"
-private const val ITEM_NAME_PARAM_NAME = "rt_nameParam"
-private val ALLOWED_OPERATOR_LIST = listOf("opDnD", "opPiP", "opInstall", "opConnect")
-
-object ItemOperatePageProvider : SettingsPageProvider {
- override val name = SettingsPageProviderEnum.ITEM_OP_PAGE.name
- override val displayName = SettingsPageProviderEnum.ITEM_OP_PAGE.displayName
- override val parameter = listOf(
- navArgument(OPERATOR_PARAM_NAME) { type = NavType.StringType },
- navArgument(ITEM_NAME_PARAM_NAME) { type = NavType.StringType },
- )
-
- override fun getTitle(arguments: Bundle?): String {
- // Operation name is not a runtime parameter, which should always available
- val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments) ?: "opInValid"
- // Item name is a runtime parameter, which could be missing
- val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, arguments) ?: "[unset]"
- return "$operation on $itemName"
- }
-
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- if (!isValidArgs(arguments)) return emptyList()
-
- val owner = createSettingsPage(arguments)
- val entryList = mutableListOf<SettingsEntry>()
- entryList.add(
- SettingsEntryBuilder.create("ItemName", owner)
- .setUiLayoutFn {
- // Item name is a runtime parameter, which needs to be read inside UiLayoutFn
- val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, it) ?: "NULL"
- Preference(
- object : PreferenceModel {
- override val title = "Item $itemName"
- }
- )
- }.build()
- )
-
- // Operation name is not a runtime parameter, which can be read outside.
- val opName = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)!!
- entryList.add(
- SettingsEntryBuilder.create("ItemOp", owner)
- .setUiLayoutFn {
- var checked by rememberSaveable { mutableStateOf(false) }
- SwitchPreference(remember {
- object : SwitchPreferenceModel {
- override val title = "Item operation: $opName"
- override val checked = { checked }
- override val onCheckedChange =
- { newChecked: Boolean -> checked = newChecked }
- }
- })
- }.build(),
- )
- return entryList
- }
-
- fun buildInjectEntry(opParam: String): SettingsEntryBuilder? {
- val arguments = bundleOf(OPERATOR_PARAM_NAME to opParam)
- if (!isValidArgs(arguments)) return null
-
- return SettingsEntryBuilder.createInject(
- owner = createSettingsPage(arguments),
- label = "ItemOp_$opParam",
- ).setUiLayoutFn {
- // Item name is a runtime parameter, which needs to be read inside UiLayoutFn
- val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, it) ?: "NULL"
- Preference(
- object : PreferenceModel {
- override val title = "item: $itemName"
- override val onClick = navigator(
- SettingsPageProviderEnum.ITEM_OP_PAGE.name + parameter.navLink(it)
- )
- }
- )
- }
- }
-
- fun isValidArgs(arguments: Bundle?): Boolean {
- val opParam = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)
- return (opParam != null && ALLOWED_OPERATOR_LIST.contains(opParam))
- }
-
- fun genRuntimeArguments(itemName: String): Bundle {
- return bundleOf(ITEM_NAME_PARAM_NAME to itemName)
- }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt
deleted file mode 100644
index e0baf86..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt
+++ /dev/null
@@ -1,56 +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.settingslib.spa.gallery.itemList
-
-import android.os.Bundle
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-
-private const val TITLE = "Operate List Main"
-
-object OperateListPageProvider : SettingsPageProvider {
- override val name = "OpList"
- private val owner = createSettingsPage()
-
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- return listOf(
- ItemListPageProvider.buildInjectEntry("opPiP")!!.setLink(fromPage = owner).build(),
- ItemListPageProvider.buildInjectEntry("opInstall")!!.setLink(fromPage = owner).build(),
- ItemListPageProvider.buildInjectEntry("opDnD")!!.setLink(fromPage = owner).build(),
- ItemListPageProvider.buildInjectEntry("opConnect")!!.setLink(fromPage = owner).build(),
- )
- }
-
- override fun getTitle(arguments: Bundle?): String {
- return TITLE
- }
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
- }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index f01ff38..9ad1c22 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -18,112 +18,69 @@
import android.os.Bundle
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+private const val TITLE = "Sample page with arguments"
+private const val STRING_PARAM_NAME = "stringParam"
+private const val INT_PARAM_NAME = "intParam"
+
object ArgumentPageProvider : SettingsPageProvider {
- // Defines all entry name in this page.
- // Note that entry name would be used in log. DO NOT change it once it is set.
- // One can still change the display name for better readability if necessary.
- private enum class EntryEnum(val displayName: String) {
- STRING_PARAM("string_param"),
- INT_PARAM("int_param"),
- }
+ override val name = "Argument"
- private fun createEntry(owner: SettingsPage, entry: EntryEnum): SettingsEntryBuilder {
- return SettingsEntryBuilder.create(owner, entry.name, entry.displayName)
- }
-
- override val name = SettingsPageProviderEnum.ARGUMENT.name
- override val displayName = SettingsPageProviderEnum.ARGUMENT.displayName
- override val parameter = ArgumentPageModel.parameter
-
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- if (!ArgumentPageModel.isValidArgument(arguments)) return emptyList()
-
- val owner = createSettingsPage(arguments)
- val entryList = mutableListOf<SettingsEntry>()
- entryList.add(
- createEntry(owner, EntryEnum.STRING_PARAM)
- // Set attributes
- .setIsSearchDataDynamic(true)
- .setSearchDataFn { ArgumentPageModel.genStringParamSearchData() }
- .setUiLayoutFn {
- // Set ui rendering
- Preference(ArgumentPageModel.create(it).genStringParamPreferenceModel())
- }.build()
- )
-
- entryList.add(
- createEntry(owner, EntryEnum.INT_PARAM)
- // Set attributes
- .setIsSearchDataDynamic(true)
- .setSearchDataFn { ArgumentPageModel.genIntParamSearchData() }
- .setUiLayoutFn {
- // Set ui rendering
- Preference(ArgumentPageModel.create(it).genIntParamPreferenceModel())
- }.build()
- )
-
- entryList.add(buildInjectEntry("foo")!!.setLink(fromPage = owner).build())
- entryList.add(buildInjectEntry("bar")!!.setLink(fromPage = owner).build())
-
- return entryList
- }
-
- fun buildInjectEntry(stringParam: String): SettingsEntryBuilder? {
- val arguments = ArgumentPageModel.buildArgument(stringParam)
- if (!ArgumentPageModel.isValidArgument(arguments)) return null
-
- return SettingsEntryBuilder.createInject(
- owner = createSettingsPage(arguments),
- label = "${name}_$stringParam",
- )
- .setSearchDataFn { ArgumentPageModel.genInjectSearchData() }
- .setUiLayoutFn {
- // Set ui rendering
- Preference(ArgumentPageModel.create(it).genInjectPreferenceModel())
- }
- }
-
- override fun getTitle(arguments: Bundle?): String {
- return ArgumentPageModel.genPageTitle()
- }
+ override val parameter = listOf(
+ navArgument(STRING_PARAM_NAME) { type = NavType.StringType },
+ navArgument(INT_PARAM_NAME) { type = NavType.IntType },
+ )
@Composable
override fun Page(arguments: Bundle?) {
- val title = remember { getTitle(arguments) }
- val entries = remember { buildEntry(arguments) }
- val rtArgNext = remember { ArgumentPageModel.buildNextArgument(arguments) }
- RegularScaffold(title) {
- for (entry in entries) {
- if (entry.toPage != null) {
- entry.UiLayout(rtArgNext)
- } else {
- entry.UiLayout()
- }
- }
- }
+ ArgumentPage(
+ stringParam = arguments!!.getString(STRING_PARAM_NAME, "default"),
+ intParam = arguments.getInt(INT_PARAM_NAME),
+ )
+ }
+
+ @Composable
+ fun EntryItem(stringParam: String, intParam: Int) {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val summary = { "$STRING_PARAM_NAME=$stringParam, $INT_PARAM_NAME=$intParam" }
+ override val onClick = navigator("$name/$stringParam/$intParam")
+ })
+ }
+}
+
+@Composable
+fun ArgumentPage(stringParam: String, intParam: Int) {
+ RegularScaffold(title = TITLE) {
+ Preference(object : PreferenceModel {
+ override val title = "String param value"
+ override val summary = { stringParam }
+ })
+
+ Preference(object : PreferenceModel {
+ override val title = "Int param value"
+ override val summary = { intParam.toString() }
+ })
+
+ ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = intParam + 1)
+
+ ArgumentPageProvider.EntryItem(stringParam = "bar", intParam = intParam + 1)
}
}
@Preview(showBackground = true)
@Composable
private fun ArgumentPagePreview() {
- SpaEnvironmentFactory.resetForPreview()
SettingsTheme {
- ArgumentPageProvider.Page(
- ArgumentPageModel.buildArgument(stringParam = "foo", intParam = 0)
- )
+ ArgumentPage(stringParam = "foo", intParam = 0)
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
deleted file mode 100644
index d763f77..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
+++ /dev/null
@@ -1,138 +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.settingslib.spa.gallery.page
-
-import android.os.Bundle
-import androidx.compose.runtime.Composable
-import androidx.lifecycle.viewmodel.compose.viewModel
-import androidx.navigation.NavType
-import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.PageModel
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.util.getIntArg
-import com.android.settingslib.spa.framework.util.getStringArg
-import com.android.settingslib.spa.framework.util.navLink
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-
-private const val TAG = "ArgumentPageModel"
-
-// Defines all the resources for this page.
-// In real Settings App, resources data is defined in xml, rather than SPP.
-private const val PAGE_TITLE = "Sample page with arguments"
-private const val STRING_PARAM_TITLE = "String param value"
-private const val INT_PARAM_TITLE = "Int param value"
-private const val STRING_PARAM_NAME = "stringParam"
-private const val INT_PARAM_NAME = "rt_intParam"
-private val ARGUMENT_PAGE_KEYWORDS = listOf("argument keyword1", "argument keyword2")
-
-class ArgumentPageModel : PageModel() {
-
- companion object {
- val parameter = listOf(
- navArgument(STRING_PARAM_NAME) { type = NavType.StringType },
- navArgument(INT_PARAM_NAME) { type = NavType.IntType },
- )
-
- fun buildArgument(stringParam: String? = null, intParam: Int? = null): Bundle {
- val args = Bundle()
- if (stringParam != null) args.putString(STRING_PARAM_NAME, stringParam)
- if (intParam != null) args.putInt(INT_PARAM_NAME, intParam)
- return args
- }
-
- fun buildNextArgument(arguments: Bundle? = null): Bundle {
- val intParam = parameter.getIntArg(INT_PARAM_NAME, arguments)
- val nextIntParam = if (intParam != null) intParam + 1 else null
- return buildArgument(intParam = nextIntParam)
- }
-
- fun isValidArgument(arguments: Bundle?): Boolean {
- val stringParam = parameter.getStringArg(STRING_PARAM_NAME, arguments)
- return (stringParam != null && listOf("foo", "bar").contains(stringParam))
- }
-
- fun genStringParamSearchData(): EntrySearchData {
- return EntrySearchData(title = STRING_PARAM_TITLE)
- }
-
- fun genIntParamSearchData(): EntrySearchData {
- return EntrySearchData(title = INT_PARAM_TITLE)
- }
-
- fun genInjectSearchData(): EntrySearchData {
- return EntrySearchData(title = PAGE_TITLE, keyword = ARGUMENT_PAGE_KEYWORDS)
- }
-
- fun genPageTitle(): String {
- return PAGE_TITLE
- }
-
- @Composable
- fun create(arguments: Bundle?): ArgumentPageModel {
- val pageModel: ArgumentPageModel = viewModel(key = arguments.toString())
- pageModel.initOnce(arguments)
- return pageModel
- }
- }
-
- private var arguments: Bundle? = null
- private var stringParam: String? = null
- private var intParam: Int? = null
-
- override fun initialize(arguments: Bundle?) {
- SpaEnvironmentFactory.instance.logger.message(
- TAG, "Initialize with args " + arguments.toString()
- )
- this.arguments = arguments
- stringParam = parameter.getStringArg(STRING_PARAM_NAME, arguments)
- intParam = parameter.getIntArg(INT_PARAM_NAME, arguments)
- }
-
- @Composable
- fun genStringParamPreferenceModel(): PreferenceModel {
- return object : PreferenceModel {
- override val title = STRING_PARAM_TITLE
- override val summary = { stringParam!! }
- }
- }
-
- @Composable
- fun genIntParamPreferenceModel(): PreferenceModel {
- return object : PreferenceModel {
- override val title = INT_PARAM_TITLE
- override val summary = { intParam!!.toString() }
- }
- }
-
- @Composable
- fun genInjectPreferenceModel(): PreferenceModel {
- val summaryArray = listOf(
- "$STRING_PARAM_NAME=" + stringParam!!,
- "$INT_PARAM_NAME=" + intParam!!
- )
- return object : PreferenceModel {
- override val title = PAGE_TITLE
- override val summary = { summaryArray.joinToString(", ") }
- override val onClick = navigator(
- SettingsPageProviderEnum.ARGUMENT.name + parameter.navLink(arguments)
- )
- }
- }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
index 345b47a..d31dab3 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
@@ -43,7 +43,7 @@
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
- SettingsEntryBuilder.create( "Some Preference", owner)
+ SettingsEntryBuilder.create("Some Preference", owner)
.setSearchDataFn { EntrySearchData(title = "Some Preference") }
.setUiLayoutFn {
Preference(remember {
@@ -58,14 +58,12 @@
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPageProvider.kt
similarity index 86%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPageProvider.kt
index ee22b96..021e84f 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPageProvider.kt
@@ -41,7 +41,7 @@
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
- SettingsEntryBuilder.create( "Lottie Illustration", owner)
+ SettingsEntryBuilder.create("Lottie Illustration", owner)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = "Lottie Illustration"
@@ -54,7 +54,7 @@
}.build()
)
entryList.add(
- SettingsEntryBuilder.create( "Image Illustration", owner)
+ SettingsEntryBuilder.create("Image Illustration", owner)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = "Image Illustration"
@@ -70,14 +70,12 @@
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt
index f1cbc37..4d47481 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt
@@ -30,9 +30,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -47,14 +45,12 @@
object LoadingBarPageProvider : SettingsPageProvider {
override val name = "LoadingBar"
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = createSettingsPage())
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPageProvider.kt
similarity index 88%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPageProvider.kt
index 9026a24..47c49fe 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPageProvider.kt
@@ -27,9 +27,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
@@ -46,14 +44,12 @@
object ProgressBarPageProvider : SettingsPageProvider {
override val name = "ProgressBar"
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = createSettingsPage())
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt
index 89b10ee..572746b 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt
@@ -117,15 +117,14 @@
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
- Preference(
- object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- }
- )
- }
+ @Composable
+ fun Entry() {
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ }
+ )
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
index 603fcee..b83a026 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
@@ -50,15 +50,12 @@
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
- Preference(
- object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- }
- )
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
index d7de9b4..3bb526e 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
@@ -23,9 +23,7 @@
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.ListPreference
@@ -33,6 +31,8 @@
import com.android.settingslib.spa.widget.preference.ListPreferenceOption
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
@@ -41,30 +41,24 @@
object ListPreferencePageProvider : SettingsPageProvider {
override val name = "ListPreference"
- private val owner = createSettingsPage()
- override fun buildEntry(arguments: Bundle?) = listOf(
- SettingsEntryBuilder.create("ListPreference", owner)
- .setUiLayoutFn {
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(TITLE) {
+ Category {
SampleListPreference()
- }.build(),
- SettingsEntryBuilder.create("ListPreference not changeable", owner)
- .setUiLayoutFn {
SampleNotChangeableListPreference()
- }.build(),
- )
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
}
+ }
}
- override fun getTitle(arguments: Bundle?) = TITLE
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
}
@Composable
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePageProvider.kt
similarity index 91%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePageProvider.kt
index 0d85c0e3..f548160 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePageProvider.kt
@@ -59,14 +59,12 @@
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
index 1626b02..831b439 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
@@ -17,45 +17,44 @@
package com.android.settingslib.spa.gallery.preference
import android.os.Bundle
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
private const val TITLE = "Category: Preference"
object PreferenceMainPageProvider : SettingsPageProvider {
override val name = "PreferenceMain"
- private val owner = createSettingsPage()
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- return listOf(
- PreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- SwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- MainSwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- TwoTargetSwitchPreferencePageProvider.buildInjectEntry()
- .setLink(fromPage = owner).build(),
- ZeroStatePreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- IntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- TopIntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- )
- }
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(TITLE) {
+ Category {
+ PreferencePageProvider.Entry()
+ ListPreferencePageProvider.Entry()
}
+ Category {
+ SwitchPreferencePageProvider.Entry()
+ MainSwitchPreferencePageProvider.Entry()
+ TwoTargetSwitchPreferencePageProvider.Entry()
+ }
+ Category {
+ ZeroStatePreferencePageProvider.Entry()
+ IntroPreferencePageProvider.Entry()
+ TopIntroPreferencePageProvider.Entry()
+ }
+ }
}
- override fun getTitle(arguments: Bundle?): String {
- return TITLE
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
deleted file mode 100644
index fc6f10f..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
+++ /dev/null
@@ -1,112 +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.settingslib.spa.gallery.preference
-
-import android.os.Bundle
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.viewModelScope
-import androidx.lifecycle.viewmodel.compose.viewModel
-import com.android.settingslib.spa.framework.common.PageModel
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-
-private const val TAG = "PreferencePageModel"
-
-class PreferencePageModel : PageModel() {
- companion object {
- // Defines all the resources for this page.
- // In real Settings App, resources data is defined in xml, rather than SPP.
- const val PAGE_TITLE = "Sample Preference"
- const val SIMPLE_PREFERENCE_TITLE = "Preference"
- const val SIMPLE_PREFERENCE_SUMMARY = "Simple summary"
- const val DISABLE_PREFERENCE_TITLE = "Disabled"
- const val DISABLE_PREFERENCE_SUMMARY = "Disabled summary"
- const val ASYNC_PREFERENCE_TITLE = "Async Preference"
- const val ASYNC_PREFERENCE_SUMMARY = "Async summary"
- const val MANUAL_UPDATE_PREFERENCE_TITLE = "Manual Updater"
- const val AUTO_UPDATE_PREFERENCE_TITLE = "Auto Updater"
- val SIMPLE_PREFERENCE_KEYWORDS = listOf("simple keyword1", "simple keyword2")
-
- @Composable
- fun create(): PreferencePageModel {
- val pageModel: PreferencePageModel = viewModel()
- pageModel.initOnce()
- return pageModel
- }
- }
-
- private val spaLogger = SpaEnvironmentFactory.instance.logger
-
- val asyncSummary = mutableStateOf("(loading)")
- val asyncEnable = mutableStateOf(false)
-
- private val manualUpdater = mutableStateOf(0)
-
- private val autoUpdater = object : MutableLiveData<String>(" ") {
- private var tick = 0
- private var updateJob: Job? = null
- override fun onActive() {
- spaLogger.message(TAG, "autoUpdater.active")
- updateJob = viewModelScope.launch(Dispatchers.IO) {
- while (true) {
- delay(1000L)
- tick++
- spaLogger.message(TAG, "autoUpdater.value $tick")
- postValue(tick.toString())
- }
- }
- }
-
- override fun onInactive() {
- spaLogger.message(TAG, "autoUpdater.inactive")
- updateJob?.cancel()
- }
- }
-
- override fun initialize(arguments: Bundle?) {
- spaLogger.message(TAG, "initialize with args " + arguments.toString())
- viewModelScope.launch(Dispatchers.IO) {
- // Loading your data here.
- delay(2000L)
- asyncSummary.value = ASYNC_PREFERENCE_SUMMARY
- asyncEnable.value = true
- }
- }
-
- fun getManualUpdaterSummary(): State<String> {
- spaLogger.message(TAG, "getManualUpdaterSummary")
- return derivedStateOf { manualUpdater.value.toString() }
- }
-
- fun manualUpdaterOnClick() {
- spaLogger.message(TAG, "manualUpdaterOnClick")
- manualUpdater.value = manualUpdater.value + 1
- }
-
- fun getAutoUpdaterSummary(): LiveData<String> {
- spaLogger.message(TAG, "getAutoUpdaterSummary")
- return autoUpdater
- }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
index 6d1d346..f7649b9 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
@@ -18,187 +18,100 @@
import android.os.Bundle
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.Autorenew
import androidx.compose.material.icons.outlined.DisabledByDefault
-import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.EntryStatusData
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.MANUAL_UPDATE_PREFERENCE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.PAGE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
import com.android.settingslib.spa.widget.ui.SettingsIcon
-
-private const val TAG = "PreferencePage"
+import kotlinx.coroutines.delay
object PreferencePageProvider : SettingsPageProvider {
- // Defines all entry name in this page.
- // Note that entry name would be used in log. DO NOT change it once it is set.
- // One can still change the display name for better readability if necessary.
- enum class EntryEnum(val displayName: String) {
- SIMPLE_PREFERENCE("preference"),
- SUMMARY_PREFERENCE("preference_with_summary"),
- SINGLE_LINE_SUMMARY_PREFERENCE("preference_with_single_line_summary"),
- DISABLED_PREFERENCE("preference_disable"),
- ASYNC_SUMMARY_PREFERENCE("preference_with_async_summary"),
- MANUAL_UPDATE_PREFERENCE("preference_actionable"),
- AUTO_UPDATE_PREFERENCE("preference_auto_update"),
- }
- override val name = SettingsPageProviderEnum.PREFERENCE.name
- override val displayName = SettingsPageProviderEnum.PREFERENCE.displayName
- private val spaLogger = SpaEnvironmentFactory.instance.logger
- private val owner = createSettingsPage()
+ override val name = "Preference"
+ private const val PAGE_TITLE = "Sample Preference"
- private fun createEntry(entry: EntryEnum): SettingsEntryBuilder {
- return SettingsEntryBuilder.create(owner, entry.name, entry.displayName)
- }
-
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val entryList = mutableListOf<SettingsEntry>()
- entryList.add(
- createEntry(EntryEnum.SIMPLE_PREFERENCE)
- .setMacro {
- spaLogger.message(TAG, "create macro for ${EntryEnum.SIMPLE_PREFERENCE}")
- SimplePreferenceMacro(title = SIMPLE_PREFERENCE_TITLE)
- }
- .setStatusDataFn { EntryStatusData(isDisabled = false) }
- .build()
- )
- entryList.add(
- createEntry(EntryEnum.SUMMARY_PREFERENCE)
- .setMacro {
- spaLogger.message(TAG, "create macro for ${EntryEnum.SUMMARY_PREFERENCE}")
- SimplePreferenceMacro(
- title = SIMPLE_PREFERENCE_TITLE,
- summary = SIMPLE_PREFERENCE_SUMMARY,
- searchKeywords = SIMPLE_PREFERENCE_KEYWORDS,
- )
- }
- .setStatusDataFn { EntryStatusData(isDisabled = true) }
- .build()
- )
- entryList.add(singleLineSummaryEntry())
- entryList.add(
- createEntry(EntryEnum.DISABLED_PREFERENCE)
- .setHasMutableStatus(true)
- .setMacro {
- spaLogger.message(TAG, "create macro for ${EntryEnum.DISABLED_PREFERENCE}")
- SimplePreferenceMacro(
- title = DISABLE_PREFERENCE_TITLE,
- summary = DISABLE_PREFERENCE_SUMMARY,
- disabled = true,
- icon = Icons.Outlined.DisabledByDefault,
- )
- }
- .setStatusDataFn { EntryStatusData(isDisabled = true) }
- .build()
- )
- entryList.add(
- createEntry(EntryEnum.ASYNC_SUMMARY_PREFERENCE)
- .setHasMutableStatus(true)
- .setSearchDataFn {
- EntrySearchData(title = ASYNC_PREFERENCE_TITLE)
- }
- .setStatusDataFn { EntryStatusData(isDisabled = false) }
- .setUiLayoutFn {
- val model = PreferencePageModel.create()
- Preference(
- object : PreferenceModel {
- override val title = ASYNC_PREFERENCE_TITLE
- override val summary = { model.asyncSummary.value }
- override val enabled = { model.asyncEnable.value }
- }
- )
- }.build()
- )
- entryList.add(
- createEntry(EntryEnum.MANUAL_UPDATE_PREFERENCE)
- .setUiLayoutFn {
- val model = PreferencePageModel.create()
- val manualUpdaterSummary = remember { model.getManualUpdaterSummary() }
- Preference(
- object : PreferenceModel {
- override val title = MANUAL_UPDATE_PREFERENCE_TITLE
- override val summary = { manualUpdaterSummary.value }
- override val onClick = { model.manualUpdaterOnClick() }
- override val icon = @Composable {
- SettingsIcon(imageVector = Icons.Outlined.TouchApp)
- }
- }
- )
- }.build()
- )
- entryList.add(
- createEntry(EntryEnum.AUTO_UPDATE_PREFERENCE)
- .setUiLayoutFn {
- val model = PreferencePageModel.create()
- val autoUpdaterSummary = remember {
- model.getAutoUpdaterSummary()
- }.observeAsState(" ")
- Preference(
- object : PreferenceModel {
- override val title = AUTO_UPDATE_PREFERENCE_TITLE
- override val summary = { autoUpdaterSummary.value }
- override val icon = @Composable {
- SettingsIcon(imageVector = Icons.Outlined.Autorenew)
- }
- }
- )
- }.build()
- )
-
- return entryList
- }
-
- private fun singleLineSummaryEntry() = createEntry(EntryEnum.SINGLE_LINE_SUMMARY_PREFERENCE)
- .setUiLayoutFn {
- val summary = stringResource(R.string.single_line_summary_preference_summary)
- Preference(
- model = object : PreferenceModel {
- override val title: String =
- stringResource(R.string.single_line_summary_preference_title)
- override val summary = { summary }
- },
- singleLineSummary = true,
- )
- }
- .build()
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = owner)
- .setMacro {
- spaLogger.message(TAG, "create macro for INJECT entry")
- SimplePreferenceMacro(
- title = PAGE_TITLE,
- clickRoute = SettingsPageProviderEnum.PREFERENCE.name
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(PAGE_TITLE) {
+ Category {
+ Preference(object : PreferenceModel {
+ override val title = "Preference"
+ })
+ Preference(object : PreferenceModel {
+ override val title = "Preference"
+ override val summary = { "Simple summary" }
+ })
+ val summary = stringResource(R.string.single_line_summary_preference_summary)
+ Preference(
+ model = object : PreferenceModel {
+ override val title =
+ stringResource(R.string.single_line_summary_preference_title)
+ override val summary = { summary }
+ },
+ singleLineSummary = true,
)
}
+ Category {
+ Preference(object : PreferenceModel {
+ override val title = "Disabled"
+ override val summary = { "Disabled summary" }
+ override val enabled = { false }
+ override val icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.DisabledByDefault)
+ }
+ })
+ }
+ Category {
+ Preference(object : PreferenceModel {
+ override val title = "Preference"
+ val asyncSummary by produceState(initialValue = " ") {
+ delay(1000L)
+ value = "Async summary"
+ }
+ override val summary = { asyncSummary }
+ })
+
+ var count by remember { mutableIntStateOf(0) }
+ Preference(object : PreferenceModel {
+ override val title = "Click me"
+ override val summary = { count.toString() }
+ override val onClick: (() -> Unit) = { count++ }
+ })
+
+ var ticks by remember { mutableIntStateOf(0) }
+ LaunchedEffect(ticks) {
+ delay(1000L)
+ ticks++
+ }
+ Preference(object : PreferenceModel {
+ override val title = "Ticker"
+ override val summary = { ticks.toString() }
+ })
+ }
+ }
}
- override fun getTitle(arguments: Bundle?): String {
- return PAGE_TITLE
+ @Composable
+ fun Entry() {
+ Preference(model = object : PreferenceModel {
+ override val title = PAGE_TITLE
+ override val onClick = navigator(name)
+ })
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt
index f2225fa..9508d50 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt
@@ -27,16 +27,15 @@
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
import com.android.settingslib.spa.widget.ui.SettingsIcon
import kotlinx.coroutines.delay
@@ -44,56 +43,26 @@
object SwitchPreferencePageProvider : SettingsPageProvider {
override val name = "SwitchPreference"
- private val owner = createSettingsPage()
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val entryList = mutableListOf<SettingsEntry>()
- entryList.add(
- SettingsEntryBuilder.create( "SwitchPreference", owner)
- .setUiLayoutFn {
- SampleSwitchPreference()
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create( "SwitchPreference with summary", owner)
- .setUiLayoutFn {
- SampleSwitchPreferenceWithSummary()
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create( "SwitchPreference with async summary", owner)
- .setUiLayoutFn {
- SampleSwitchPreferenceWithAsyncSummary()
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create( "SwitchPreference not changeable", owner)
- .setUiLayoutFn {
- SampleNotChangeableSwitchPreference()
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create( "SwitchPreference with icon", owner)
- .setUiLayoutFn {
- SampleSwitchPreferenceWithIcon()
- }.build()
- )
-
- return entryList
- }
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(TITLE) {
+ Category {
+ SampleSwitchPreference()
+ SampleSwitchPreferenceWithSummary()
+ SampleSwitchPreferenceWithAsyncSummary()
+ SampleNotChangeableSwitchPreference()
+ SampleSwitchPreferenceWithIcon()
}
+ }
}
- override fun getTitle(arguments: Bundle?): String {
- return TITLE
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
index b251266..ee08e30 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
@@ -50,15 +50,12 @@
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
- Preference(
- object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- }
- )
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt
index 19de31d..1a89bb2 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt
@@ -25,66 +25,40 @@
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
import kotlinx.coroutines.delay
private const val TITLE = "Sample TwoTargetSwitchPreference"
object TwoTargetSwitchPreferencePageProvider : SettingsPageProvider {
override val name = "TwoTargetSwitchPreference"
- private val owner = createSettingsPage()
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val entryList = mutableListOf<SettingsEntry>()
- entryList.add(
- SettingsEntryBuilder.create( "TwoTargetSwitchPreference", owner)
- .setUiLayoutFn {
- SampleTwoTargetSwitchPreference()
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create( "TwoTargetSwitchPreference with summary", owner)
- .setUiLayoutFn {
- SampleTwoTargetSwitchPreferenceWithSummary()
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create( "TwoTargetSwitchPreference with async summary", owner)
- .setUiLayoutFn {
- SampleTwoTargetSwitchPreferenceWithAsyncSummary()
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create( "TwoTargetSwitchPreference not changeable", owner)
- .setUiLayoutFn {
- SampleNotChangeableTwoTargetSwitchPreference()
- }.build()
- )
-
- return entryList
- }
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(TITLE) {
+ Category {
+ SampleTwoTargetSwitchPreference()
+ SampleTwoTargetSwitchPreferenceWithSummary()
+ SampleTwoTargetSwitchPreferenceWithAsyncSummary()
+ SampleNotChangeableTwoTargetSwitchPreference()
}
+ }
}
- override fun getTitle(arguments: Bundle?): String {
- return TITLE
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
index 4a9c5c8..04b5ceb 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
@@ -53,14 +53,12 @@
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt
index 66cc38f..c9a6557 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt
@@ -17,7 +17,7 @@
package com.android.settingslib.spa.gallery.scaffold
import android.os.Bundle
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
@@ -34,13 +34,13 @@
ScrollablePagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
)
- fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
override fun getTitle(arguments: Bundle?) = TITLE
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt
index eac06e3..0d7cad10 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt
@@ -18,9 +18,7 @@
import android.os.Bundle
import androidx.compose.runtime.Composable
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -32,15 +30,13 @@
object SearchScaffoldPageProvider : SettingsPageProvider {
override val name = "SearchScaffold"
- private val owner = createSettingsPage()
-
- fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
@Composable
override fun Page(arguments: Bundle?) {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
index a0ab2ce..7b02fcb 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
@@ -27,9 +27,7 @@
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.gallery.R
@@ -49,15 +47,13 @@
object SuwScaffoldPageProvider : SettingsPageProvider {
override val name = "SuwScaffold"
- private val owner = createSettingsPage()
-
- fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
@Composable
override fun Page(arguments: Bundle?) {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
similarity index 81%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
index 7a1fad0..4d3a78a5 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
@@ -19,7 +19,6 @@
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.EntrySearchData
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -31,7 +30,6 @@
import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.Category
-import com.android.settingslib.spa.widget.ui.CategoryTitle
private const val TITLE = "Sample Category"
@@ -39,15 +37,14 @@
override val name = "Category"
private val owner = createSettingsPage()
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
+ @Composable
+ fun Entry() {
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
}
- .setSearchDataFn { EntrySearchData(title = TITLE) }
+ )
}
override fun getTitle(arguments: Bundle?): String {
@@ -70,7 +67,6 @@
SettingsEntryBuilder.create("Preference 3", owner)
.setMacro { SimplePreferenceMacro(title = "Preference 2", summary = "Summary 3") }
.build()
-
)
entryList.add(
SettingsEntryBuilder.create("Preference 4", owner)
@@ -84,11 +80,11 @@
override fun Page(arguments: Bundle?) {
val entries = buildEntry(arguments)
RegularScaffold(title = getTitle(arguments)) {
- CategoryTitle("Category A")
- entries[0].UiLayout()
- entries[1].UiLayout()
-
- Category("Category B") {
+ Category("Category A") {
+ entries[0].UiLayout()
+ entries[1].UiLayout()
+ }
+ Category {
entries[2].UiLayout()
entries[3].UiLayout()
}
@@ -99,7 +95,5 @@
@Preview(showBackground = true)
@Composable
private fun SpinnerPagePreview() {
- SettingsTheme {
- SpinnerPageProvider.Page(null)
- }
+ SettingsTheme { CategoryPageProvider.Page(null) }
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt
index f897d8c..e919129 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt
@@ -21,10 +21,7 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.preference.Preference
@@ -37,17 +34,12 @@
object CopyablePageProvider : SettingsPageProvider {
override val name = "Copyable"
- private val owner = createSettingsPage()
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
- .setSearchDataFn { EntrySearchData(title = TITLE) }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
@Composable
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt
index 5c5c504..7a4b632 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt
@@ -23,9 +23,7 @@
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
@@ -39,14 +37,12 @@
object SpinnerPageProvider : SettingsPageProvider {
override val name = "Spinner"
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = createSettingsPage())
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index f8c791a..ab95162 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -24,6 +24,7 @@
val paddingExtraSmall = 4.dp
val paddingSmall = if (isSpaExpressiveEnabled) 8.dp else 4.dp
val paddingExtraSmall5 = 10.dp
+ val paddingExtraSmall6 = 12.dp
val paddingLarge = 16.dp
val paddingExtraLarge = 24.dp
@@ -36,9 +37,9 @@
val itemIconSize = 24.dp
val itemIconContainerSize = 72.dp
- val itemPaddingStart = paddingExtraLarge
+ val itemPaddingStart = if (isSpaExpressiveEnabled) paddingLarge else paddingExtraLarge
val itemPaddingEnd = paddingLarge
- val itemPaddingVertical = paddingLarge
+ val itemPaddingVertical = if (isSpaExpressiveEnabled) paddingExtraSmall6 else paddingLarge
val itemPadding = PaddingValues(
start = itemPaddingStart,
top = itemPaddingVertical,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
index f7c5414..c787715 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
@@ -24,5 +24,7 @@
val CornerMedium = RoundedCornerShape(12.dp)
+ val categoryCorner = RoundedCornerShape(20.dp)
+
val CornerExtraLarge = RoundedCornerShape(28.dp)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt
index 185fd29..38707b0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt
@@ -57,6 +57,7 @@
import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraSmall
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
import com.android.settingslib.spa.widget.ui.SettingsBody
import com.android.settingslib.spa.widget.ui.SettingsTitle
@@ -159,7 +160,9 @@
@Composable
fun BannerTitleHeader(title: String, onDismiss: (() -> Unit)? = null) {
Row(Modifier.fillMaxWidth()) {
- Box(modifier = Modifier.weight(1f)) { SettingsTitle(title) }
+ Box(modifier = Modifier.weight(1f)) {
+ Text(text = title, style = MaterialTheme.typography.titleMedium.toSemiBoldWeight())
+ }
Spacer(modifier = Modifier.padding(SettingsDimension.paddingSmall))
DismissButton(onDismiss)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
index 5bb57b8..203a8bd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
@@ -56,6 +56,7 @@
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.theme.divider
import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
data class ActionButton(
val text: String,
@@ -129,7 +130,7 @@
Text(
text = actionButton.text,
textAlign = TextAlign.Center,
- style = MaterialTheme.typography.labelMedium,
+ style = MaterialTheme.typography.labelLarge.toSemiBoldWeight(),
)
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
index 265864e..490936f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
@@ -26,6 +26,7 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@@ -99,7 +100,16 @@
dismissButton?.let {
{ if (isSpaExpressiveEnabled) DismissButton(it) else Button(it) }
},
- title = title?.let { { CenterRow { Text(it) } } },
+ title =
+ title?.let {
+ {
+ CenterRow {
+ if (isSpaExpressiveEnabled)
+ Text(it, style = MaterialTheme.typography.bodyLarge)
+ else Text(it)
+ }
+ }
+ },
text =
text?.let {
{ CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index 23a8e78..c68ec78 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.widget.preference
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -25,16 +26,20 @@
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
+import com.android.settingslib.spa.framework.theme.SettingsShape
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
import com.android.settingslib.spa.widget.ui.SettingsTitle
@Composable
@@ -51,10 +56,17 @@
widget: @Composable () -> Unit = {},
) {
Row(
- modifier = modifier
- .fillMaxWidth()
- .semantics(mergeDescendants = true) {}
- .padding(end = paddingEnd),
+ modifier =
+ modifier
+ .fillMaxWidth()
+ .semantics(mergeDescendants = true) {}
+ .then(
+ if (isSpaExpressiveEnabled)
+ Modifier.clip(SettingsShape.CornerExtraSmall)
+ .background(MaterialTheme.colorScheme.surfaceBright)
+ else Modifier
+ )
+ .padding(end = paddingEnd),
verticalAlignment = Alignment.CenterVertically,
) {
val alphaModifier = Modifier.alphaForEnabled(enabled())
@@ -63,20 +75,14 @@
title = title,
titleContentDescription = titleContentDescription,
subTitle = subTitle,
- modifier = alphaModifier
- .weight(1f)
- .padding(vertical = paddingVertical),
+ modifier = alphaModifier.weight(1f).padding(vertical = paddingVertical),
)
widget()
}
}
@Composable
-internal fun BaseIcon(
- icon: @Composable (() -> Unit)?,
- modifier: Modifier,
- paddingStart: Dp,
-) {
+internal fun BaseIcon(icon: @Composable (() -> Unit)?, modifier: Modifier, paddingStart: Dp) {
if (icon != null) {
Box(
modifier = modifier.size(SettingsDimension.itemIconContainerSize),
@@ -107,11 +113,6 @@
@Composable
private fun BaseLayoutPreview() {
SettingsTheme {
- BaseLayout(
- title = "Title",
- subTitle = {
- HorizontalDivider(thickness = 10.dp)
- }
- )
+ BaseLayout(title = "Title", subTitle = { HorizontalDivider(thickness = 10.dp) })
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
index 22a5755..7707376 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
@@ -36,6 +36,7 @@
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
@Composable
fun IntroPreference(
@@ -112,7 +113,7 @@
Text(
text = title,
textAlign = TextAlign.Center,
- style = MaterialTheme.typography.titleLarge,
+ style = MaterialTheme.typography.titleLarge.toSemiBoldWeight(),
color = MaterialTheme.colorScheme.onSurface,
)
}
@@ -126,7 +127,7 @@
Text(
text = description,
textAlign = TextAlign.Center,
- style = MaterialTheme.typography.titleMedium,
+ style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = SettingsDimension.paddingExtraSmall),
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
index 3f2e772..b771f36 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
@@ -47,6 +47,7 @@
import androidx.graphics.shapes.RoundedPolygon
import androidx.graphics.shapes.star
import androidx.graphics.shapes.toPath
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
@Composable
fun ZeroStatePreference(icon: ImageVector, text: String? = null, description: String? = null) {
@@ -80,7 +81,7 @@
Text(
text = text,
textAlign = TextAlign.Center,
- style = MaterialTheme.typography.titleMedium,
+ style = MaterialTheme.typography.titleMedium.toSemiBoldWeight(),
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 24.dp),
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
index 48cd145..6c5581f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
@@ -16,9 +16,13 @@
package com.android.settingslib.spa.widget.ui
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -27,25 +31,31 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsShape
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
-/**
- * A category title that is placed before a group of similar items.
- */
+/** A category title that is placed before a group of similar items. */
@Composable
fun CategoryTitle(title: String) {
Text(
text = title,
- modifier = Modifier.padding(
- start = SettingsDimension.itemPaddingStart,
- top = 20.dp,
- end = SettingsDimension.itemPaddingEnd,
- bottom = 8.dp,
- ),
+ modifier =
+ Modifier.padding(
+ start = SettingsDimension.itemPaddingStart,
+ top = 20.dp,
+ end =
+ if (isSpaExpressiveEnabled) SettingsDimension.paddingSmall
+ else SettingsDimension.itemPaddingEnd,
+ bottom = 8.dp,
+ ),
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.labelMedium,
)
@@ -56,14 +66,31 @@
* visually separates groups of items.
*/
@Composable
-fun Category(title: String, content: @Composable ColumnScope.() -> Unit) {
- Column {
+fun Category(title: String? = null, content: @Composable ColumnScope.() -> Unit) {
+ Column(
+ modifier =
+ if (isSpaExpressiveEnabled)
+ Modifier.padding(
+ horizontal = SettingsDimension.paddingLarge,
+ vertical = SettingsDimension.paddingSmall,
+ )
+ else Modifier
+ ) {
var displayTitle by remember { mutableStateOf(false) }
- if (displayTitle) CategoryTitle(title = title)
+ if (title != null && displayTitle) CategoryTitle(title = title)
Column(
- modifier = Modifier.onGloballyPositioned { coordinates ->
- displayTitle = coordinates.size.height > 0
- },
+ modifier =
+ Modifier.onGloballyPositioned { coordinates ->
+ displayTitle = coordinates.size.height > 0
+ }
+ .then(
+ if (isSpaExpressiveEnabled)
+ Modifier.fillMaxWidth().clip(SettingsShape.categoryCorner)
+ else Modifier
+ ),
+ verticalArrangement =
+ if (isSpaExpressiveEnabled) Arrangement.spacedBy(SettingsDimension.paddingTiny)
+ else Arrangement.Top,
content = content,
)
}
@@ -73,6 +100,21 @@
@Composable
private fun CategoryPreview() {
SettingsTheme {
- CategoryTitle("Appearance")
+ Category("Appearance") {
+ Preference(
+ object : PreferenceModel {
+ override val title = "Title"
+ override val summary = { "Summary" }
+ }
+ )
+ Preference(
+ object : PreferenceModel {
+ override val title = "Title"
+ override val summary = { "Summary" }
+ override val icon =
+ @Composable { SettingsIcon(imageVector = Icons.Outlined.TouchApp) }
+ }
+ )
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 616ab07..612c193 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -709,6 +709,9 @@
@WorkerThread
public static boolean hasConnectedBroadcastSourceForBtDevice(
@Nullable BluetoothDevice device, @Nullable LocalBluetoothManager localBtManager) {
+ if (Flags.audioSharingHysteresisModeFix()) {
+ return hasActiveLocalBroadcastSourceForBtDevice(device, localBtManager);
+ }
LocalBluetoothLeBroadcastAssistant assistant =
localBtManager == null
? null
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index a3f9e51..364e95c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -52,6 +52,7 @@
import androidx.annotation.RequiresApi;
import com.android.settingslib.R;
+import com.android.settingslib.flags.Flags;
import com.google.common.collect.ImmutableList;
@@ -1134,20 +1135,8 @@
Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null");
return;
}
- List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices();
- List<BluetoothDevice> devicesInSharing =
- connectedDevices.stream()
- .filter(
- bluetoothDevice -> {
- List<BluetoothLeBroadcastReceiveState> sourceList =
- mServiceBroadcastAssistant.getAllSources(
- bluetoothDevice);
- return !sourceList.isEmpty()
- && sourceList.stream()
- .anyMatch(BluetoothUtils::isConnected);
- })
- .collect(Collectors.toList());
- if (devicesInSharing.isEmpty()) {
+ List<BluetoothDevice> devicesInBroadcast = getDevicesInBroadcast();
+ if (devicesInBroadcast.isEmpty()) {
Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no sinks in broadcast");
return;
}
@@ -1156,7 +1145,7 @@
BluetoothDevice targetDevice = null;
// Find the earliest connected device in sharing session.
int targetDeviceIdx = -1;
- for (BluetoothDevice device : devicesInSharing) {
+ for (BluetoothDevice device : devicesInBroadcast) {
if (devices.contains(device)) {
int idx = devices.indexOf(device);
if (idx > targetDeviceIdx) {
@@ -1169,10 +1158,6 @@
Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, target is null");
return;
}
- Log.d(
- TAG,
- "updateFallbackActiveDeviceIfNeeded, set active device: "
- + targetDevice.getAnonymizedAddress());
CachedBluetoothDevice targetCachedDevice = mDeviceManager.findDevice(targetDevice);
if (targetCachedDevice == null) {
Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, fail to find cached bt device");
@@ -1180,16 +1165,37 @@
}
int fallbackActiveGroupId = getFallbackActiveGroupId();
if (fallbackActiveGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
- && getGroupId(targetCachedDevice) == fallbackActiveGroupId) {
+ && BluetoothUtils.getGroupId(targetCachedDevice) == fallbackActiveGroupId) {
Log.d(
TAG,
"Skip updateFallbackActiveDeviceIfNeeded, already is fallback: "
+ fallbackActiveGroupId);
return;
}
+ Log.d(
+ TAG,
+ "updateFallbackActiveDeviceIfNeeded, set active device: "
+ + targetDevice.getAnonymizedAddress());
targetCachedDevice.setActive();
}
+ private List<BluetoothDevice> getDevicesInBroadcast() {
+ boolean hysteresisModeFixEnabled = Flags.audioSharingHysteresisModeFix();
+ List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices();
+ return connectedDevices.stream()
+ .filter(
+ bluetoothDevice -> {
+ List<BluetoothLeBroadcastReceiveState> sourceList =
+ mServiceBroadcastAssistant.getAllSources(
+ bluetoothDevice);
+ return !sourceList.isEmpty() && sourceList.stream().anyMatch(
+ source -> hysteresisModeFixEnabled
+ ? BluetoothUtils.isSourceMatched(source, mBroadcastId)
+ : BluetoothUtils.isConnected(source));
+ })
+ .collect(Collectors.toList());
+ }
+
private int getFallbackActiveGroupId() {
return Settings.Secure.getInt(
mContext.getContentResolver(),
@@ -1197,23 +1203,6 @@
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
}
- private int getGroupId(CachedBluetoothDevice cachedDevice) {
- int groupId = cachedDevice.getGroupId();
- String anonymizedAddress = cachedDevice.getDevice().getAnonymizedAddress();
- if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
- Log.d(TAG, "getGroupId by CSIP profile for device: " + anonymizedAddress);
- return groupId;
- }
- for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
- if (profile instanceof LeAudioProfile) {
- Log.d(TAG, "getGroupId by LEA profile for device: " + anonymizedAddress);
- return ((LeAudioProfile) profile).getGroupId(cachedDevice.getDevice());
- }
- }
- Log.d(TAG, "getGroupId return invalid id for device: " + anonymizedAddress);
- return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
- }
-
private void notifyBroadcastStateChange(@BroadcastState int state) {
if (!mContext.getPackageName().equals(SETTINGS_PKG)) {
Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
index 5656f38..3627669 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
@@ -63,7 +63,8 @@
},
moreSettingsHelpItem = readParcelable(
DeviceSettingItem::class.java.classLoader
- )
+ ),
+ extras = readBundle((Bundle::class.java.classLoader)) ?: Bundle.EMPTY,
)
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 8eedb35..0e060df 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -47,6 +47,7 @@
import android.util.Pair;
import com.android.internal.R;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.widget.AdaptiveIcon;
import com.google.common.collect.ImmutableList;
@@ -605,6 +606,7 @@
@Test
public void testHasConnectedBroadcastSource_leadDeviceConnectedToBroadcastSource() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
CachedBluetoothDevice memberCachedDevice = mock(CachedBluetoothDevice.class);
BluetoothDevice memberDevice = mock(BluetoothDevice.class);
@@ -630,6 +632,7 @@
@Test
public void testHasConnectedBroadcastSource_memberDeviceConnectedToBroadcastSource() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
CachedBluetoothDevice memberCachedDevice = mock(CachedBluetoothDevice.class);
BluetoothDevice memberDevice = mock(BluetoothDevice.class);
@@ -655,6 +658,7 @@
@Test
public void testHasConnectedBroadcastSource_deviceNotConnectedToBroadcastSource() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
List<Long> bisSyncState = new ArrayList<>();
@@ -672,6 +676,7 @@
@Test
public void testHasConnectedBroadcastSourceForBtDevice_deviceConnectedToBroadcastSource() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
List<Long> bisSyncState = new ArrayList<>();
bisSyncState.add(1L);
when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
@@ -688,6 +693,7 @@
@Test
public void testHasConnectedBroadcastSourceForBtDevice_deviceNotConnectedToBroadcastSource() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
List<Long> bisSyncState = new ArrayList<>();
when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
@@ -702,6 +708,106 @@
}
@Test
+ public void hasConnectedBroadcastSource_hysteresisFix_leadDeviceHasActiveLocalSource() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ CachedBluetoothDevice memberCachedDevice = mock(CachedBluetoothDevice.class);
+ BluetoothDevice memberDevice = mock(BluetoothDevice.class);
+ when(memberCachedDevice.getDevice()).thenReturn(memberDevice);
+ Set<CachedBluetoothDevice> memberCachedDevices = new HashSet<>();
+ memberCachedDevices.add(memberCachedDevice);
+ when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(memberCachedDevices);
+
+
+ when(mBroadcast.getLatestBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+ when(mLeBroadcastReceiveState.getBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+ when(mAssistant.getAllSources(memberDevice)).thenReturn(Collections.emptyList());
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isTrue();
+ }
+
+ @Test
+ public void hasConnectedBroadcastSource_hysteresisFix_memberDeviceHasActiveLocalSource() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ CachedBluetoothDevice memberCachedDevice = mock(CachedBluetoothDevice.class);
+ BluetoothDevice memberDevice = mock(BluetoothDevice.class);
+ when(memberCachedDevice.getDevice()).thenReturn(memberDevice);
+ Set<CachedBluetoothDevice> memberCachedDevices = new HashSet<>();
+ memberCachedDevices.add(memberCachedDevice);
+ when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(memberCachedDevices);
+
+ when(mBroadcast.getLatestBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+ when(mLeBroadcastReceiveState.getBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(memberDevice)).thenReturn(sourceList);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(Collections.emptyList());
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isTrue();
+ }
+
+ @Test
+ public void hasConnectedBroadcastSource_hysteresisFix_deviceNoActiveLocalSource() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+
+ when(mBroadcast.getLatestBroadcastId()).thenReturn(UNKNOWN_VALUE_PLACEHOLDER);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isFalse();
+ }
+
+ @Test
+ public void hasConnectedBroadcastSourceForBtDevice_hysteresisFix_deviceHasActiveLocalSource() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ when(mBroadcast.getLatestBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+ when(mLeBroadcastReceiveState.getBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSourceForBtDevice(
+ mBluetoothDevice, mLocalBluetoothManager))
+ .isTrue();
+ }
+
+ @Test
+ public void hasConnectedBroadcastSourceForBtDevice_hysteresisFix_deviceNoActiveLocalSource() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ when(mBroadcast.getLatestBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+ when(mLeBroadcastReceiveState.getBroadcastId()).thenReturn(UNKNOWN_VALUE_PLACEHOLDER);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSourceForBtDevice(
+ mBluetoothDevice, mLocalBluetoothManager))
+ .isFalse();
+ }
+
+ @Test
public void testHasActiveLocalBroadcastSourceForBtDevice_hasActiveLocalSource() {
when(mBroadcast.getLatestBroadcastId()).thenReturn(TEST_BROADCAST_ID);
when(mLeBroadcastReceiveState.getBroadcastId()).thenReturn(TEST_BROADCAST_ID);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
index 7f17293..ebaad34 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
@@ -86,6 +86,7 @@
assertThat(fromParcel.moreSettingsHelpItem?.packageName).isEqualTo("package_name_2")
assertThat(fromParcel.moreSettingsHelpItem?.className).isEqualTo("class_name_2")
assertThat(fromParcel.moreSettingsHelpItem?.intentAction).isEqualTo("intent_action_2")
+ assertThat(fromParcel.extras.getString("key1")).isEqualTo("value1")
}
private fun writeAndRead(item: DeviceSettingsConfig): DeviceSettingsConfig {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppPreferenceTest.java
deleted file mode 100644
index 6c8fd50..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppPreferenceTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2021 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.settingslib.widget;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.view.View;
-
-import androidx.preference.PreferenceViewHolder;
-
-import com.android.settingslib.widget.preference.app.R;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-@RunWith(RobolectricTestRunner.class)
-public class AppPreferenceTest {
-
- private Context mContext;
- private View mRootView;
- private AppPreference mPref;
- private PreferenceViewHolder mHolder;
-
- @Before
- public void setUp() {
- mContext = RuntimeEnvironment.application;
- mRootView = View.inflate(mContext, R.layout.preference_app, null /* parent */);
- mHolder = PreferenceViewHolder.createInstanceForTests(mRootView);
- mPref = new AppPreference(mContext);
- }
-
- @Test
- public void setProgress_showProgress() {
- mPref.setProgress(1);
- mPref.onBindViewHolder(mHolder);
-
- assertThat(mHolder.findViewById(android.R.id.progress).getVisibility())
- .isEqualTo(View.VISIBLE);
- }
-
- @Test
- public void foobar_testName() {
- float iconSize = mContext.getResources().getDimension(com.android.settingslib.widget.theme.R.dimen.secondary_app_icon_size);
- assertThat(Float.floatToIntBits(iconSize)).isEqualTo(Float.floatToIntBits(32));
- }
-}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 88cc152..cb1411b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -559,6 +559,13 @@
}
flag {
+ name: "volume_redesign"
+ namespace: "systemui"
+ description: "Enables Volume BC25 visuals update"
+ bug: "368308908"
+}
+
+flag {
name: "clipboard_shared_transitions"
namespace: "systemui"
description: "Show shared transitions from clipboard"
@@ -1118,6 +1125,16 @@
}
flag {
+ name: "media_controls_umo_inflation_in_background"
+ namespace: "systemui"
+ description: "Inflate UMO in background thread"
+ bug: "368514198"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
namespace: "systemui"
name: "enable_view_capture_tracing"
description: "Enables view capture tracing in System UI."
@@ -1421,4 +1438,4 @@
metadata {
purpose: PURPOSE_BUGFIX
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index f4d1242..bcd3337 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -274,7 +274,7 @@
if (layoutDirection == LayoutDirection.Rtl)
screenWidth - offset.x
else offset.x,
- offset.y
+ offset.y,
) - contentOffset
val index = firstIndexAtOffset(gridState, adjustedOffset)
val key =
@@ -310,6 +310,9 @@
it.changedToUp() || it.changedToUpIgnoreConsumed()
}
)
+
+ // Reset state once touch ends.
+ viewModel.onResetTouchState()
}
}
}
@@ -330,7 +333,7 @@
if (layoutDirection == LayoutDirection.Rtl)
screenWidth - offset.x
else offset.x,
- offset.y
+ offset.y,
) - it.positionInWindow() - contentOffset
}
val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
@@ -344,14 +347,11 @@
}
}
}
- },
+ }
) {
AccessibilityContainer(viewModel) {
if (!viewModel.isEditMode && isEmptyState) {
- EmptyStateCta(
- contentPadding = contentPadding,
- viewModel = viewModel,
- )
+ EmptyStateCta(contentPadding = contentPadding, viewModel = viewModel)
} else {
val slideOffsetInPx =
with(LocalDensity.current) { Dimensions.SlideOffsetY.toPx().toInt() }
@@ -364,7 +364,7 @@
) +
slideInVertically(
animationSpec = tween(durationMillis = 1000, easing = Emphasized),
- initialOffsetY = { -slideOffsetInPx }
+ initialOffsetY = { -slideOffsetInPx },
),
exit =
fadeOut(
@@ -372,7 +372,7 @@
) +
slideOutVertically(
animationSpec = tween(durationMillis = 1000, easing = Emphasized),
- targetOffsetY = { -slideOffsetInPx }
+ targetOffsetY = { -slideOffsetInPx },
),
modifier = Modifier.fillMaxSize(),
) {
@@ -389,7 +389,7 @@
removeEnabled = removeButtonEnabled,
offset =
gridCoordinates?.let { it.positionInWindow() + offset },
- containerToCheck = removeButtonCoordinates
+ containerToCheck = removeButtonCoordinates,
)
},
gridState = gridState,
@@ -410,7 +410,7 @@
enter =
fadeIn(animationSpec = tween(durationMillis = 250, easing = LinearEasing)) +
slideInVertically(
- animationSpec = tween(durationMillis = 1000, easing = Emphasized),
+ animationSpec = tween(durationMillis = 1000, easing = Emphasized)
),
exit =
fadeOut(animationSpec = tween(durationMillis = 167, easing = LinearEasing)) +
@@ -434,7 +434,7 @@
viewModel.setSelectedKey(null)
}
},
- removeEnabled = removeButtonEnabled
+ removeEnabled = removeButtonEnabled,
)
}
}
@@ -451,7 +451,7 @@
title = stringResource(id = R.string.dialog_title_to_allow_any_widget),
positiveButtonText = stringResource(id = R.string.button_text_to_open_settings),
onConfirm = viewModel::onEnableWidgetDialogConfirm,
- onCancel = viewModel::onEnableWidgetDialogCancel
+ onCancel = viewModel::onEnableWidgetDialogCancel,
)
EnableWidgetDialog(
@@ -460,7 +460,7 @@
title = stringResource(id = R.string.work_mode_off_title),
positiveButtonText = stringResource(id = R.string.work_mode_turn_on),
onConfirm = viewModel::onEnableWorkProfileDialogConfirm,
- onCancel = viewModel::onEnableWorkProfileDialogCancel
+ onCancel = viewModel::onEnableWorkProfileDialogCancel,
)
}
@@ -509,7 +509,7 @@
imageVector = Icons.Outlined.Widgets,
contentDescription = null,
tint = colors.primary,
- modifier = Modifier.size(32.dp)
+ modifier = Modifier.size(32.dp),
)
Spacer(modifier = Modifier.height(16.dp))
Text(
@@ -527,7 +527,7 @@
Modifier.padding(horizontal = 26.dp, vertical = 16.dp)
.widthIn(min = 200.dp)
.heightIn(min = 56.dp),
- onClick = { onButtonClicked() }
+ onClick = { onButtonClicked() },
) {
Text(
stringResource(R.string.communal_widgets_disclaimer_button),
@@ -540,7 +540,7 @@
@Composable
private fun ObserveScrollEffect(
gridState: LazyGridState,
- communalViewModel: BaseCommunalViewModel
+ communalViewModel: BaseCommunalViewModel,
) {
LaunchedEffect(gridState) {
@@ -667,7 +667,7 @@
rememberGridDragDropState(
gridState = gridState,
contentListState = contentListState,
- updateDragPositionForRemove = updateDragPositionForRemove
+ updateDragPositionForRemove = updateDragPositionForRemove,
)
gridModifier =
gridModifier
@@ -677,7 +677,7 @@
LocalLayoutDirection.current,
screenWidth,
contentOffset,
- viewModel
+ viewModel,
)
// for widgets dropped from other activities
val dragAndDropTargetState =
@@ -709,11 +709,7 @@
contentType = { _, item -> item.key },
span = { _, item -> GridItemSpan(item.size.span) },
) { index, item ->
- val size =
- SizeF(
- Dimensions.CardWidth.value,
- item.size.dp().value,
- )
+ val size = SizeF(Dimensions.CardWidth.value, item.size.dp().value)
val cardModifier = Modifier.requiredSize(width = size.width.dp, height = size.height.dp)
if (viewModel.isEditMode && dragDropState != null) {
val selected = item.key == selectedKey.value
@@ -765,16 +761,13 @@
* The empty state displays a fullscreen call-to-action (CTA) tile when no widgets are available.
*/
@Composable
-private fun EmptyStateCta(
- contentPadding: PaddingValues,
- viewModel: BaseCommunalViewModel,
-) {
+private fun EmptyStateCta(contentPadding: PaddingValues, viewModel: BaseCommunalViewModel) {
val colors = LocalAndroidColorScheme.current
Card(
modifier = Modifier.height(hubDimensions.GridHeight).padding(contentPadding),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
border = BorderStroke(3.adjustedDp, colors.secondary),
- shape = RoundedCornerShape(size = 80.adjustedDp)
+ shape = RoundedCornerShape(size = 80.adjustedDp),
) {
Column(
modifier = Modifier.fillMaxSize().padding(horizontal = 110.adjustedDp),
@@ -788,10 +781,7 @@
textAlign = TextAlign.Center,
color = colors.secondary,
)
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.Center,
- ) {
+ Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Button(
modifier = Modifier.height(56.dp),
colors =
@@ -799,17 +789,13 @@
containerColor = colors.primary,
contentColor = colors.onPrimary,
),
- onClick = {
- viewModel.onOpenWidgetEditor(
- shouldOpenWidgetPickerOnStart = true,
- )
- },
+ onClick = { viewModel.onOpenWidgetEditor(shouldOpenWidgetPickerOnStart = true) },
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription =
stringResource(R.string.label_for_button_in_empty_state_cta),
- modifier = Modifier.size(24.dp)
+ modifier = Modifier.size(24.dp),
)
Spacer(Modifier.width(ButtonDefaults.IconSpacing))
Text(
@@ -835,7 +821,7 @@
setToolbarSize: (toolbarSize: IntSize) -> Unit,
setRemoveButtonCoordinates: (coordinates: LayoutCoordinates?) -> Unit,
onOpenWidgetPicker: () -> Unit,
- onEditDone: () -> Unit
+ onEditDone: () -> Unit,
) {
if (!removeEnabled) {
// Clear any existing coordinates when remove is not enabled.
@@ -844,7 +830,7 @@
val removeButtonAlpha: Float by
animateFloatAsState(
targetValue = if (removeEnabled) 1f else 0.5f,
- label = "RemoveButtonAlphaAnimation"
+ label = "RemoveButtonAlphaAnimation",
)
Box(
@@ -855,7 +841,7 @@
start = Dimensions.ToolbarPaddingHorizontal,
end = Dimensions.ToolbarPaddingHorizontal,
)
- .onSizeChanged { setToolbarSize(it) },
+ .onSizeChanged { setToolbarSize(it) }
) {
val addWidgetText = stringResource(R.string.hub_mode_add_widget_button_text)
ToolbarButton(
@@ -864,16 +850,14 @@
onClick = onOpenWidgetPicker,
) {
Icon(Icons.Default.Add, null)
- Text(
- text = addWidgetText,
- )
+ Text(text = addWidgetText)
}
AnimatedVisibility(
modifier = Modifier.align(Alignment.Center),
visible = removeEnabled,
enter = fadeIn(),
- exit = fadeOut()
+ exit = fadeOut(),
) {
Button(
onClick = onRemoveClicked,
@@ -887,20 +871,18 @@
if (removeEnabled) {
setRemoveButtonCoordinates(it)
}
- }
+ },
) {
Row(
horizontalArrangement =
Arrangement.spacedBy(
ButtonDefaults.IconSpacing,
- Alignment.CenterHorizontally
+ Alignment.CenterHorizontally,
),
- verticalAlignment = Alignment.CenterVertically
+ verticalAlignment = Alignment.CenterVertically,
) {
Icon(Icons.Default.Close, contentDescription = null)
- Text(
- text = stringResource(R.string.button_to_remove_widget),
- )
+ Text(text = stringResource(R.string.button_to_remove_widget))
}
}
}
@@ -911,9 +893,7 @@
onClick = onEditDone,
) {
Icon(Icons.Default.Check, contentDescription = null)
- Text(
- text = stringResource(R.string.hub_mode_editing_exit_button_text),
- )
+ Text(text = stringResource(R.string.hub_mode_editing_exit_button_text))
}
}
}
@@ -926,14 +906,14 @@
isPrimary: Boolean = true,
onClick: () -> Unit,
modifier: Modifier = Modifier,
- content: @Composable RowScope.() -> Unit
+ content: @Composable RowScope.() -> Unit,
) {
val colors = LocalAndroidColorScheme.current
AnimatedVisibility(
visible = isPrimary,
modifier = modifier,
enter = fadeIn(),
- exit = fadeOut()
+ exit = fadeOut(),
) {
Button(
onClick = onClick,
@@ -943,7 +923,7 @@
Row(
horizontalArrangement =
Arrangement.spacedBy(ButtonDefaults.IconSpacing, Alignment.CenterHorizontally),
- verticalAlignment = Alignment.CenterVertically
+ verticalAlignment = Alignment.CenterVertically,
) {
content()
}
@@ -954,21 +934,18 @@
visible = !isPrimary,
modifier = modifier,
enter = fadeIn(),
- exit = fadeOut()
+ exit = fadeOut(),
) {
OutlinedButton(
onClick = onClick,
- colors =
- ButtonDefaults.outlinedButtonColors(
- contentColor = colors.onPrimaryContainer,
- ),
+ colors = ButtonDefaults.outlinedButtonColors(contentColor = colors.onPrimaryContainer),
border = BorderStroke(width = 2.0.dp, color = colors.primary),
contentPadding = Dimensions.ButtonPadding,
) {
Row(
horizontalArrangement =
Arrangement.spacedBy(ButtonDefaults.IconSpacing, Alignment.CenterHorizontally),
- verticalAlignment = Alignment.CenterVertically
+ verticalAlignment = Alignment.CenterVertically,
) {
content()
}
@@ -1041,7 +1018,7 @@
size =
Size(width = size.width + padding * 2, height = size.height + padding * 2),
cornerRadius = CornerRadius(37.adjustedDp.toPx()),
- style = Stroke(width = 3.adjustedDp.toPx())
+ style = Stroke(width = 3.adjustedDp.toPx()),
)
}
)
@@ -1061,7 +1038,7 @@
containerColor = colors.primary,
contentColor = colors.onPrimary,
),
- shape = RoundedCornerShape(68.adjustedDp, 34.adjustedDp, 68.adjustedDp, 34.adjustedDp)
+ shape = RoundedCornerShape(68.adjustedDp, 34.adjustedDp, 68.adjustedDp, 34.adjustedDp),
) {
Column(
modifier =
@@ -1081,7 +1058,7 @@
style = MaterialTheme.typography.titleLarge,
fontSize = nonScalableTextSize(22.dp),
lineHeight = nonScalableTextSize(28.dp),
- modifier = Modifier.verticalScroll(rememberScrollState()).weight(1F)
+ modifier = Modifier.verticalScroll(rememberScrollState()).weight(1F),
)
Spacer(modifier = Modifier.size(16.adjustedDp))
Row(
@@ -1093,15 +1070,12 @@
LocalDensity provides
Density(
LocalDensity.current.density,
- LocalDensity.current.fontScale.coerceIn(0f, 1.25f)
+ LocalDensity.current.fontScale.coerceIn(0f, 1.25f),
)
) {
OutlinedButton(
modifier = Modifier.fillMaxHeight().weight(1F),
- colors =
- ButtonDefaults.buttonColors(
- contentColor = colors.onPrimary,
- ),
+ colors = ButtonDefaults.buttonColors(contentColor = colors.onPrimary),
border = BorderStroke(width = 1.0.dp, color = colors.primaryContainer),
onClick = viewModel::onDismissCtaTile,
contentPadding = PaddingValues(0.dp, 0.dp, 0.dp, 0.dp),
@@ -1259,7 +1233,7 @@
visible = selected,
model = model,
widgetConfigurator = widgetConfigurator,
- modifier = Modifier.align(Alignment.BottomEnd)
+ modifier = Modifier.align(Alignment.BottomEnd),
)
}
}
@@ -1289,14 +1263,14 @@
containerColor = colors.primary,
contentColor = colors.onPrimary,
disabledContainerColor = Color.Transparent,
- disabledContentColor = Color.Transparent
+ disabledContentColor = Color.Transparent,
),
onClick = { scope.launch { widgetConfigurator.configureWidget(model.appWidgetId) } },
) {
Icon(
imageVector = Icons.Outlined.Edit,
contentDescription = stringResource(id = R.string.edit_widget),
- modifier = Modifier.padding(12.adjustedDp)
+ modifier = Modifier.padding(12.adjustedDp),
)
}
}
@@ -1323,13 +1297,13 @@
.background(
color = MaterialTheme.colorScheme.surfaceVariant,
shape =
- RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+ RoundedCornerShape(dimensionResource(system_app_widget_background_radius)),
)
.clickable(
enabled = !viewModel.isEditMode,
interactionSource = null,
indication = null,
- onClick = viewModel::onOpenEnableWidgetDialog
+ onClick = viewModel::onOpenEnableWidgetDialog,
),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
@@ -1360,7 +1334,7 @@
modifier =
modifier.background(
color = MaterialTheme.colorScheme.surfaceVariant,
- shape = RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+ shape = RoundedCornerShape(dimensionResource(system_app_widget_background_radius)),
),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
@@ -1418,7 +1392,7 @@
MotionEvent.ACTION_MOVE,
change.position.x,
change.position.y,
- 0
+ 0,
)
viewModel.mediaHost.hostView.dispatchTouchEvent(event)
event.recycle()
@@ -1429,12 +1403,12 @@
layoutParams =
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.MATCH_PARENT
+ FrameLayout.LayoutParams.MATCH_PARENT,
)
}
viewModel.mediaHost.hostView
},
- onReset = {}
+ onReset = {},
)
}
@@ -1462,7 +1436,7 @@
) {
viewModel.changeScene(
CommunalScenes.Blank,
- "closed by accessibility"
+ "closed by accessibility",
)
true
},
@@ -1471,7 +1445,7 @@
) {
viewModel.onOpenWidgetEditor()
true
- }
+ },
)
}
}
@@ -1514,7 +1488,7 @@
start = Dimensions.ToolbarPaddingHorizontal,
end = Dimensions.ToolbarPaddingHorizontal,
top = verticalPadding + toolbarHeight,
- bottom = verticalPadding
+ bottom = verticalPadding,
)
}
@@ -1523,7 +1497,7 @@
return with(LocalDensity.current) {
ContentPaddingInPx(
start = paddingValues.calculateStartPadding(LocalLayoutDirection.current).toPx(),
- top = paddingValues.calculateTopPadding().toPx()
+ top = paddingValues.calculateTopPadding().toPx(),
)
}
}
@@ -1536,7 +1510,7 @@
fun isPointerWithinEnabledRemoveButton(
removeEnabled: Boolean,
offset: Offset?,
- containerToCheck: LayoutCoordinates?
+ containerToCheck: LayoutCoordinates?,
): Boolean {
if (!removeEnabled || offset == null || containerToCheck == null) {
return false
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 9891025..367faed 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
@@ -188,38 +188,47 @@
return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation)
}
+ private fun resolveSwipeSource(startedPosition: Offset?): SwipeSource.Resolved? {
+ if (startedPosition == null) return null
+ return layoutImpl.swipeSourceDetector.source(
+ layoutSize = layoutImpl.lastSize,
+ position = startedPosition.round(),
+ density = layoutImpl.density,
+ orientation = orientation,
+ )
+ }
+
+ private fun resolveSwipe(
+ pointersDown: Int,
+ fromSource: SwipeSource.Resolved?,
+ isUpOrLeft: Boolean,
+ ): Swipe.Resolved {
+ return Swipe.Resolved(
+ direction =
+ when (orientation) {
+ Orientation.Horizontal ->
+ if (isUpOrLeft) {
+ SwipeDirection.Resolved.Left
+ } else {
+ SwipeDirection.Resolved.Right
+ }
+
+ Orientation.Vertical ->
+ if (isUpOrLeft) {
+ SwipeDirection.Resolved.Up
+ } else {
+ SwipeDirection.Resolved.Down
+ }
+ },
+ pointerCount = pointersDown,
+ fromSource = fromSource,
+ )
+ }
+
private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes {
- val fromSource =
- startedPosition?.let { position ->
- layoutImpl.swipeSourceDetector.source(
- layoutImpl.lastSize,
- position.round(),
- layoutImpl.density,
- orientation,
- )
- }
-
- val upOrLeft =
- Swipe.Resolved(
- direction =
- when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Resolved.Left
- Orientation.Vertical -> SwipeDirection.Resolved.Up
- },
- pointerCount = pointersDown,
- fromSource = fromSource,
- )
-
- val downOrRight =
- Swipe.Resolved(
- direction =
- when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Resolved.Right
- Orientation.Vertical -> SwipeDirection.Resolved.Down
- },
- pointerCount = pointersDown,
- fromSource = fromSource,
- )
+ val fromSource = resolveSwipeSource(startedPosition)
+ val upOrLeft = resolveSwipe(pointersDown, fromSource, isUpOrLeft = true)
+ val downOrRight = resolveSwipe(pointersDown, fromSource, isUpOrLeft = false)
return if (fromSource == null) {
Swipes(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index c163c6f..0490a26 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -350,7 +350,7 @@
testScope.runTest {
underTest.performDotFeedback(null)
- assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR)
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR_DISCRETE)
assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
index e25c1a7..d5020a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -109,7 +109,9 @@
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
underTest.start()
kosmos.communalSceneRepository.setTransitionState(sceneTransitions)
- testScope.launch { keyguardTransitionRepository.emitInitialStepsFromOff(LOCKSCREEN) }
+ testScope.launch {
+ keyguardTransitionRepository.emitInitialStepsFromOff(LOCKSCREEN, testSetup = true)
+ }
}
/** Transition from blank to glanceable hub. This is the default case. */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
index ab33269..d7fe263 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
@@ -16,10 +16,10 @@
package com.android.systemui.education.domain.ui.view
+import android.app.Dialog
import android.app.Notification
import android.app.NotificationManager
import android.content.applicationContext
-import android.widget.Toast
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -34,11 +34,13 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -51,6 +53,7 @@
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -63,10 +66,12 @@
private val minDurationForNextEdu =
KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds
private lateinit var underTest: ContextualEduUiCoordinator
- @Mock private lateinit var toast: Toast
+ @Mock private lateinit var dialog: Dialog
@Mock private lateinit var notificationManager: NotificationManager
+ @Mock private lateinit var accessibilityManagerWrapper: AccessibilityManagerWrapper
@get:Rule val mockitoRule = MockitoJUnit.rule()
private var toastContent = ""
+ private val timeoutMillis = 3500L
@Before
fun setUp() {
@@ -75,30 +80,35 @@
interactor.updateTouchpadFirstConnectionTime()
}
+ whenever(accessibilityManagerWrapper.getRecommendedTimeoutMillis(any(), any()))
+ .thenReturn(timeoutMillis.toInt())
+
val viewModel =
ContextualEduViewModel(
kosmos.applicationContext.resources,
- kosmos.keyboardTouchpadEduInteractor
+ kosmos.keyboardTouchpadEduInteractor,
+ accessibilityManagerWrapper,
)
+
underTest =
ContextualEduUiCoordinator(
kosmos.applicationCoroutineScope,
viewModel,
kosmos.applicationContext,
notificationManager
- ) { content ->
- toastContent = content
- toast
+ ) { model ->
+ toastContent = model.message
+ dialog
}
underTest.start()
kosmos.keyboardTouchpadEduInteractor.start()
}
@Test
- fun showToastOnNewEdu() =
+ fun showDialogOnNewEdu() =
testScope.runTest {
triggerEducation(BACK)
- verify(toast).show()
+ verify(dialog).show()
}
@Test
@@ -111,6 +121,14 @@
}
@Test
+ fun dismissDialogAfterTimeout() =
+ testScope.runTest {
+ triggerEducation(BACK)
+ advanceTimeBy(timeoutMillis + 1)
+ verify(dialog).dismiss()
+ }
+
+ @Test
fun verifyBackEduToastContent() =
testScope.runTest {
triggerEducation(BACK)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index 29035ce..d97909a1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -19,17 +19,22 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -38,11 +43,13 @@
import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.Transition
import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -80,6 +87,7 @@
alternateBouncerInteractor = kosmos.alternateBouncerInteractor,
shadeInteractor = { kosmos.shadeInteractor },
keyguardInteractor = { kosmos.keyguardInteractor },
+ sceneInteractor = { kosmos.sceneInteractor },
)
}
@@ -178,7 +186,11 @@
)
assertThat(executeDismissAction).isNull()
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
kosmos.setSceneTransition(Idle(Scenes.Gone))
+ kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
assertThat(executeDismissAction).isNotNull()
}
@@ -301,4 +313,78 @@
underTest.setKeyguardDone(KeyguardDone.IMMEDIATE)
assertThat(keyguardDoneTiming).isEqualTo(KeyguardDone.IMMEDIATE)
}
+
+ @Test
+ @EnableSceneContainer
+ fun dismissAction_executesBeforeItsReset_sceneContainerOn_swipeAuth_fromQsScene() =
+ testScope.runTest {
+ val canSwipeToEnter by collectLastValue(kosmos.deviceEntryInteractor.canSwipeToEnter)
+ val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(currentScene!!)
+ )
+ kosmos.sceneInteractor.setTransitionState(transitionState)
+ val executeDismissAction by collectLastValue(underTest.executeDismissAction)
+ val resetDismissAction by collectLastValue(underTest.resetDismissAction)
+ assertThat(executeDismissAction).isNull()
+ assertThat(resetDismissAction).isNull()
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ assertThat(canSwipeToEnter).isTrue()
+ kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "")
+ transitionState.value = ObservableTransitionState.Idle(Scenes.QuickSettings)
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+
+ assertThat(executeDismissAction).isNull()
+ assertThat(resetDismissAction).isNull()
+
+ val dismissAction =
+ DismissAction.RunImmediately(
+ onDismissAction = { KeyguardDone.LATER },
+ onCancelAction = {},
+ message = "message",
+ willAnimateOnLockscreen = true,
+ )
+ underTest.setDismissAction(dismissAction)
+ // Should still be null because the transition to Gone has not yet happened.
+ assertThat(executeDismissAction).isNull()
+ assertThat(resetDismissAction).isNull()
+
+ transitionState.value =
+ ObservableTransitionState.Transition.ChangeScene(
+ fromScene = Scenes.QuickSettings,
+ toScene = Scenes.Gone,
+ currentScene = flowOf(Scenes.QuickSettings),
+ currentOverlays = emptySet(),
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ runCurrent()
+ assertThat(executeDismissAction).isNull()
+ assertThat(resetDismissAction).isNull()
+
+ transitionState.value =
+ ObservableTransitionState.Transition.ChangeScene(
+ fromScene = Scenes.QuickSettings,
+ toScene = Scenes.Gone,
+ currentScene = flowOf(Scenes.Gone),
+ currentOverlays = emptySet(),
+ progress = flowOf(1f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ runCurrent()
+ assertThat(executeDismissAction).isNotNull()
+ assertThat(resetDismissAction).isNull()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
index 129752e..aab46d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
@@ -22,6 +22,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.testKosmos
import org.junit.Before
import org.junit.Test
@@ -44,6 +45,7 @@
KeyguardBlueprintViewModel(
handler = kosmos.fakeExecutorHandler,
keyguardBlueprintInteractor = keyguardBlueprintInteractor,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index e6ea64f..d0da2e9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -89,9 +89,12 @@
}
@Test
- fun lockscreenFadeOut() =
+ fun lockscreenFadeOut_shadeNotExpanded() =
testScope.runTest {
val values by collectValues(underTest.lockscreenAlpha)
+ shadeExpanded(false)
+ runCurrent()
+
repository.sendTransitionSteps(
steps =
listOf(
@@ -104,10 +107,34 @@
),
testScope = testScope,
)
- // Only 5 values should be present, since the dream overlay runs for a small fraction
- // of the overall animation time
assertThat(values.size).isEqualTo(5)
- values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+ assertThat(values[0]).isEqualTo(1f)
+ assertThat(values[1]).isEqualTo(1f)
+ assertThat(values[2]).isIn(Range.open(0f, 1f))
+ assertThat(values[3]).isIn(Range.open(0f, 1f))
+ assertThat(values[4]).isEqualTo(0f)
+ }
+
+ @Test
+ fun lockscreenFadeOut_shadeExpanded() =
+ testScope.runTest {
+ val values by collectValues(underTest.lockscreenAlpha)
+ shadeExpanded(true)
+ runCurrent()
+
+ repository.sendTransitionSteps(
+ steps =
+ listOf(
+ step(0f, TransitionState.STARTED), // Should start running here...
+ step(0f),
+ step(.1f),
+ step(.4f),
+ step(.7f), // ...up to here
+ step(1f),
+ ),
+ testScope = testScope,
+ )
+ values.forEach { assertThat(it).isEqualTo(0f) }
}
@Test
@@ -115,7 +142,7 @@
testScope.runTest {
configurationRepository.setDimensionPixelSize(
R.dimen.lockscreen_to_occluded_transition_lockscreen_translation_y,
- 100
+ 100,
)
val values by collectValues(underTest.lockscreenTranslationY)
repository.sendTransitionSteps(
@@ -138,7 +165,7 @@
testScope.runTest {
configurationRepository.setDimensionPixelSize(
R.dimen.lockscreen_to_occluded_transition_lockscreen_translation_y,
- 100
+ 100,
)
val values by collectValues(underTest.lockscreenTranslationY)
repository.sendTransitionSteps(
@@ -171,7 +198,7 @@
listOf(
step(0f, TransitionState.STARTED),
step(.5f),
- step(1f, TransitionState.FINISHED)
+ step(1f, TransitionState.FINISHED),
),
testScope = testScope,
)
@@ -228,7 +255,7 @@
to = KeyguardState.OCCLUDED,
value = value,
transitionState = state,
- ownerName = "LockscreenToOccludedTransitionViewModelTest"
+ ownerName = "LockscreenToOccludedTransitionViewModelTest",
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelTest.kt
new file mode 100644
index 0000000..0b7a38e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.platform.test.flag.junit.FlagsParameterization
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+class OffToLockscreenTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+ val repository = kosmos.fakeKeyguardTransitionRepository
+ lateinit var underTest: OffToLockscreenTransitionViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ @Before
+ fun setup() {
+ underTest = kosmos.offToLockscreenTransitionViewModel
+ }
+
+ @Test
+ fun lockscreenAlpha() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.lockscreenAlpha)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0f))
+ assertThat(alpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(0.66f))
+ assertThat(alpha).isIn(Range.open(.1f, .9f))
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(alpha).isEqualTo(1f)
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING,
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.OFF,
+ to = KeyguardState.LOCKSCREEN,
+ value = value,
+ transitionState = state,
+ ownerName = "OffToLockscreenTransitionViewModelTest",
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index 3e3aa4f..e12c67b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -18,7 +18,7 @@
import com.android.wm.shell.recents.RecentTasks
import com.android.wm.shell.shared.GroupedRecentTaskInfo
import com.android.wm.shell.shared.split.SplitBounds
-import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import java.util.function.Consumer
@@ -268,7 +268,7 @@
GroupedRecentTaskInfo.forSplitTasks(
createTaskInfo(taskId1, userId1, isVisible),
createTaskInfo(taskId2, userId2, isVisible),
- SplitBounds(Rect(), Rect(), taskId1, taskId2, SNAP_TO_50_50)
+ SplitBounds(Rect(), Rect(), taskId1, taskId2, SNAP_TO_2_50_50)
)
private fun createTaskInfo(taskId: Int, userId: Int, isVisible: Boolean = false) =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
new file mode 100644
index 0000000..4bbdfa4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.viewmodel
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.testing.TestLifecycleOwner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class AbstractQSFragmentComposeViewModelTest : SysuiTestCase() {
+ protected val kosmos = testKosmos()
+
+ protected val lifecycleOwner =
+ TestLifecycleOwner(
+ initialState = Lifecycle.State.CREATED,
+ coroutineDispatcher = kosmos.testDispatcher,
+ )
+
+ protected val underTest by lazy {
+ kosmos.qsFragmentComposeViewModelFactory.create(lifecycleOwner.lifecycleScope)
+ }
+
+ @Before
+ fun setUp() {
+ Dispatchers.setMain(kosmos.testDispatcher)
+ }
+
+ @After
+ fun teardown() {
+ Dispatchers.resetMain()
+ }
+
+ protected inline fun TestScope.testWithinLifecycle(
+ crossinline block: suspend TestScope.() -> TestResult
+ ): TestResult {
+ return runTest {
+ lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED)
+ lifecycleOwner.lifecycleScope.launch { underTest.activate() }
+ block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt
new file mode 100644
index 0000000..57a9377
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.viewmodel
+
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.sysuiStatusBarStateController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@SmallTest
+@RunWith(Parameterized::class)
+@RunWithLooper
+class QSFragmentComposeViewModelForceQSTest(private val testData: TestData) :
+ AbstractQSFragmentComposeViewModelTest() {
+
+ @Test
+ fun forceQs_orRealExpansion() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ val expansionState by collectLastValue(underTest.expansionState)
+
+ with(testData) {
+ sysuiStatusBarStateController.setState(statusBarState)
+ underTest.isQSExpanded = expanded
+ underTest.isStackScrollerOverscrolling = stackScrollerOverScrolling
+ fakeDeviceEntryRepository.setBypassEnabled(bypassEnabled)
+ underTest.isTransitioningToFullShade = transitioningToFullShade
+ underTest.isInSplitShade = inSplitShade
+
+ underTest.qsExpansionValue = EXPANSION
+ assertThat(expansionState!!.progress)
+ .isEqualTo(if (expectedForceQS) 1f else EXPANSION)
+ }
+ }
+ }
+
+ data class TestData(
+ val statusBarState: Int,
+ val expanded: Boolean,
+ val stackScrollerOverScrolling: Boolean,
+ val bypassEnabled: Boolean,
+ val transitioningToFullShade: Boolean,
+ val inSplitShade: Boolean,
+ ) {
+ private val inKeyguard = statusBarState == StatusBarState.KEYGUARD
+
+ private val showCollapsedOnKeyguard =
+ bypassEnabled || (transitioningToFullShade && !inSplitShade)
+
+ val expectedForceQS =
+ (expanded || stackScrollerOverScrolling) && (inKeyguard && !showCollapsedOnKeyguard)
+ }
+
+ companion object {
+ private const val EXPANSION = 0.3f
+
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun createTestData(): List<TestData> {
+ return statusBarStates.flatMap { statusBarState ->
+ (0u..31u).map { bitfield ->
+ TestData(
+ statusBarState,
+ expanded = (bitfield or 1u) == 1u,
+ stackScrollerOverScrolling = (bitfield or 2u) == 1u,
+ bypassEnabled = (bitfield or 4u) == 1u,
+ transitioningToFullShade = (bitfield or 8u) == 1u,
+ inSplitShade = (bitfield or 16u) == 1u,
+ )
+ }
+ }
+ }
+
+ private val statusBarStates =
+ setOf(StatusBarState.SHADE, StatusBarState.KEYGUARD, StatusBarState.SHADE_LOCKED)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 6f20e70..c19e4b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -19,64 +19,28 @@
import android.app.StatusBarManager
import android.content.testableContext
import android.testing.TestableLooper.RunWithLooper
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.fgsManagerController
+import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
import com.android.systemui.res.R
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
import com.android.systemui.statusbar.sysuiStatusBarStateController
-import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestResult
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
-import org.junit.After
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
-@OptIn(ExperimentalCoroutinesApi::class)
-class QSFragmentComposeViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
-
- private val lifecycleOwner =
- TestLifecycleOwner(
- initialState = Lifecycle.State.CREATED,
- coroutineDispatcher = kosmos.testDispatcher,
- )
-
- private val underTest by lazy {
- kosmos.qsFragmentComposeViewModelFactory.create(lifecycleOwner.lifecycleScope)
- }
-
- @Before
- fun setUp() {
- Dispatchers.setMain(kosmos.testDispatcher)
- }
-
- @After
- fun teardown() {
- Dispatchers.resetMain()
- }
+class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() {
@Test
fun qsExpansionValueChanges_correctExpansionState() =
@@ -205,16 +169,30 @@
}
}
- private inline fun TestScope.testWithinLifecycle(
- crossinline block: suspend TestScope.() -> TestResult
- ): TestResult {
- return runTest {
- lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED)
- block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) }
+ @Test
+ fun squishinessInExpansion_setInInteractor() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ val squishiness by collectLastValue(tileSquishinessInteractor.squishiness)
+
+ underTest.squishinessFractionValue = 0.3f
+ assertThat(squishiness).isWithin(epsilon).of(0.3f.constrainSquishiness())
+
+ underTest.squishinessFractionValue = 0f
+ assertThat(squishiness).isWithin(epsilon).of(0f.constrainSquishiness())
+
+ underTest.squishinessFractionValue = 1f
+ assertThat(squishiness).isWithin(epsilon).of(1f.constrainSquishiness())
+ }
}
- }
companion object {
private const val QS_DISABLE_FLAG = StatusBarManager.DISABLE2_QUICK_SETTINGS
+
+ private fun Float.constrainSquishiness(): Float {
+ return (0.1f + this * 0.9f).coerceIn(0f, 1f)
+ }
+
+ private const val epsilon = 0.001f
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
index 9e90090..a9a527f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
@@ -22,10 +22,8 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
-import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
+import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -44,8 +42,7 @@
}
}
- private val underTest =
- with(kosmos) { InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel) }
+ private val underTest = kosmos.infiniteGridLayout
@Test
fun correctPagination_underOnePage_sameOrder() =
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e94248d..629c94f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -2047,4 +2047,6 @@
<!-- SliceView icon size -->
<dimen name="abc_slice_big_pic_min_height">64dp</dimen>
<dimen name="abc_slice_big_pic_max_height">64dp</dimen>
+
+ <dimen name="contextual_edu_dialog_bottom_margin">70dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 75389b1..c76b35f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3818,6 +3818,8 @@
<!-- Main text of the one line view of a redacted notification -->
<string name="redacted_notification_single_line_text">Unlock to view</string>
+ <!-- Content description for contextual education dialog [CHAR LIMIT=NONE] -->
+ <string name="contextual_education_dialog_title">Contextual education</string>
<!-- Education notification title for Back [CHAR_LIMIT=100] -->
<string name="back_edu_notification_title">Use your touchpad to go back</string>
<!-- Education notification text for Back [CHAR_LIMIT=100] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index a02c354..b34d6e4 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1720,4 +1720,10 @@
<style name="ShortcutHelperTheme" parent="@style/ShortcutHelperThemeCommon">
<item name="android:windowLightNavigationBar">true</item>
</style>
+
+ <style name="ContextualEduDialog" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar">
+ <!-- To make the dialog wrap to content when the education text is short -->
+ <item name="windowMinWidthMajor">0%</item>
+ <item name="windowMinWidthMinor">0%</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index 55ccaa6..92bc95a 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -70,4 +70,12 @@
* {@link #onBootCompleted()} will never be called before {@link #start()}. */
default void onBootCompleted() {
}
+
+ /** No op implementation that can be used when feature flagging on the Dagger Module level. */
+ CoreStartable NOP = new Nop();
+
+ class Nop implements CoreStartable {
+ @Override
+ public void start() {}
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index 76df9c9..fb00d6e 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -75,6 +75,9 @@
* touches are consumed.
*/
public class TouchMonitor {
+ // An incrementing id used to identify the touch monitor instance.
+ private static int sNextInstanceId = 0;
+
private final Logger mLogger;
// This executor is used to protect {@code mActiveTouchSessions} from being modified
// concurrently. Any operation that adds or removes values should use this executor.
@@ -138,7 +141,7 @@
completer.set(predecessor);
}
- if (mActiveTouchSessions.isEmpty()) {
+ if (mActiveTouchSessions.isEmpty() && mInitialized) {
if (mStopMonitoringPending) {
stopMonitoring(false);
} else {
@@ -271,7 +274,7 @@
@Override
public void onDestroy(LifecycleOwner owner) {
- stopMonitoring(true);
+ destroy();
}
};
@@ -279,6 +282,11 @@
* When invoked, instantiates a new {@link InputSession} to monitor touch events.
*/
private void startMonitoring() {
+ if (!mInitialized) {
+ mLogger.w("attempting to startMonitoring when not initialized");
+ return;
+ }
+
mLogger.i("startMonitoring(): monitoring started");
stopMonitoring(true);
@@ -587,7 +595,7 @@
mDisplayHelper = displayHelper;
mWindowManagerService = windowManagerService;
mConfigurationInteractor = configurationInteractor;
- mLoggingName = loggingName + ":TouchMonitor";
+ mLoggingName = loggingName + ":TouchMonitor[" + sNextInstanceId++ + "]";
mLogger = new Logger(logBuffer, mLoggingName);
}
@@ -613,7 +621,8 @@
*/
public void destroy() {
if (!mInitialized) {
- throw new IllegalStateException("TouchMonitor not initialized");
+ // In the case that we've already been destroyed, this is a no-op
+ return;
}
stopMonitoring(true);
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
index b8c30fe..d6b9211 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
@@ -69,7 +69,7 @@
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
)
} else {
- msdlPlayer.get().playToken(MSDLToken.DRAG_INDICATOR)
+ msdlPlayer.get().playToken(MSDLToken.DRAG_INDICATOR_DISCRETE)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index cbea876..8da4d46 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -30,7 +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.tutorial.KeyboardTouchpadTutorialCoreStartable
+import com.android.systemui.haptics.msdl.MSDLCoreStartable
import com.android.systemui.keyboard.KeyboardUI
import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable
import com.android.systemui.keyguard.KeyguardViewConfigurator
@@ -323,4 +323,9 @@
@IntoMap
@ClassKey(BatteryControllerStartable::class)
abstract fun bindsBatteryControllerStartable(impl: BatteryControllerStartable): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(MSDLCoreStartable::class)
+ abstract fun bindMSDLCoreStartable(impl: MSDLCoreStartable): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt
new file mode 100644
index 0000000..287e85c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.systemui.education.ui.view
+
+import android.app.AlertDialog
+import android.content.Context
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import android.widget.ToastPresenter
+import com.android.systemui.education.ui.viewmodel.ContextualEduToastViewModel
+import com.android.systemui.res.R
+
+class ContextualEduDialog(context: Context, private val model: ContextualEduToastViewModel) :
+ AlertDialog(context, R.style.ContextualEduDialog) {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setUpWindowProperties()
+ setWindowPosition()
+ // title is used for a11y announcement
+ window?.setTitle(context.getString(R.string.contextual_education_dialog_title))
+ // TODO: b/369791926 - replace the below toast view with a custom dialog view
+ val toastView = ToastPresenter.getTextToastView(context, model.message)
+ setView(toastView)
+ super.onCreate(savedInstanceState)
+ }
+
+ private fun setUpWindowProperties() {
+ window?.apply {
+ setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG)
+ clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ }
+ setCanceledOnTouchOutside(false)
+ }
+
+ private fun setWindowPosition() {
+ window?.apply {
+ setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
+ this.attributes =
+ WindowManager.LayoutParams().apply {
+ width = WindowManager.LayoutParams.WRAP_CONTENT
+ height = WindowManager.LayoutParams.WRAP_CONTENT
+ copyFrom(attributes)
+ y =
+ context.resources.getDimensionPixelSize(
+ R.dimen.contextual_edu_dialog_bottom_margin
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
index e62b26b..913ecdd 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
@@ -16,6 +16,7 @@
package com.android.systemui.education.ui.view
+import android.app.Dialog
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
@@ -24,7 +25,6 @@
import android.content.Intent
import android.os.Bundle
import android.os.UserHandle
-import android.widget.Toast
import androidx.core.app.NotificationCompat
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
@@ -49,7 +49,7 @@
private val viewModel: ContextualEduViewModel,
private val context: Context,
private val notificationManager: NotificationManager,
- private val createToast: (String) -> Toast
+ private val createDialog: (ContextualEduToastViewModel) -> Dialog,
) : CoreStartable {
companion object {
@@ -69,16 +69,23 @@
viewModel,
context,
notificationManager,
- createToast = { message -> Toast.makeText(context, message, Toast.LENGTH_LONG) }
+ createDialog = { model -> ContextualEduDialog(context, model) },
)
+ var dialog: Dialog? = null
+
override fun start() {
createEduNotificationChannel()
applicationScope.launch {
viewModel.eduContent.collect { contentModel ->
- when (contentModel) {
- is ContextualEduToastViewModel -> showToast(contentModel)
- is ContextualEduNotificationViewModel -> showNotification(contentModel)
+ if (contentModel != null) {
+ when (contentModel) {
+ is ContextualEduToastViewModel -> showDialog(contentModel)
+ is ContextualEduNotificationViewModel -> showNotification(contentModel)
+ }
+ } else {
+ dialog?.dismiss()
+ dialog = null
}
}
}
@@ -95,9 +102,9 @@
notificationManager.createNotificationChannel(channel)
}
- private fun showToast(model: ContextualEduToastViewModel) {
- val toast = createToast(model.message)
- toast.show()
+ private fun showDialog(model: ContextualEduToastViewModel) {
+ dialog = createDialog(model)
+ dialog?.show()
}
private fun showNotification(model: ContextualEduNotificationViewModel) {
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
index cd4a8ad..32e7f41 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.education.ui.viewmodel
import android.content.res.Resources
+import android.view.accessibility.AccessibilityManager
import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.contextualeducation.GestureType.HOME
@@ -27,23 +28,63 @@
import com.android.systemui.education.shared.model.EducationInfo
import com.android.systemui.education.shared.model.EducationUiType
import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import javax.inject.Inject
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
@SysUISingleton
class ContextualEduViewModel
@Inject
-constructor(@Main private val resources: Resources, interactor: KeyboardTouchpadEduInteractor) {
- val eduContent: Flow<ContextualEduContentViewModel> =
- interactor.educationTriggered.filterNotNull().map {
- if (it.educationUiType == EducationUiType.Notification) {
- ContextualEduNotificationViewModel(getEduTitle(it), getEduContent(it), it.userId)
- } else {
- ContextualEduToastViewModel(getEduContent(it), it.userId)
+constructor(
+ @Main private val resources: Resources,
+ interactor: KeyboardTouchpadEduInteractor,
+ private val accessibilityManagerWrapper: AccessibilityManagerWrapper,
+) {
+
+ companion object {
+ const val DEFAULT_DIALOG_TIMEOUT_MILLIS = 3500
+ }
+
+ private val timeoutMillis: Long
+ get() =
+ accessibilityManagerWrapper
+ .getRecommendedTimeoutMillis(
+ DEFAULT_DIALOG_TIMEOUT_MILLIS,
+ AccessibilityManager.FLAG_CONTENT_TEXT,
+ )
+ .toLong()
+
+ val eduContent: Flow<ContextualEduContentViewModel?> =
+ interactor.educationTriggered
+ .filterNotNull()
+ .map {
+ if (it.educationUiType == EducationUiType.Notification) {
+ ContextualEduNotificationViewModel(
+ getEduTitle(it),
+ getEduContent(it),
+ it.userId,
+ )
+ } else {
+ ContextualEduToastViewModel(getEduContent(it), it.userId)
+ }
+ }
+ .timeout(timeoutMillis, emitAfterTimeout = null)
+
+ private fun <T> Flow<T>.timeout(timeoutMillis: Long, emitAfterTimeout: T): Flow<T> {
+ return flatMapLatest {
+ flow {
+ emit(it)
+ delay(timeoutMillis)
+ emit(emitAfterTimeout)
}
}
+ }
private fun getEduContent(educationInfo: EducationInfo): String {
val resourceId =
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/MSDLCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/MSDLCoreStartable.kt
new file mode 100644
index 0000000..58736c60
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/MSDLCoreStartable.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.haptics.msdl
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.google.android.msdl.domain.MSDLPlayer
+import com.google.android.msdl.logging.MSDLHistoryLogger
+import java.io.PrintWriter
+import javax.inject.Inject
+
+@SysUISingleton
+class MSDLCoreStartable @Inject constructor(private val msdlPlayer: MSDLPlayer) : CoreStartable {
+ override fun start() {}
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("MSDLPlayer history of the last ${MSDLHistoryLogger.HISTORY_SIZE} events:")
+ msdlPlayer.getHistory().forEach { event -> pw.println("$event") }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 0a38ce0..9c7cc81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -147,6 +147,7 @@
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -265,6 +266,7 @@
private static final int NOTIFY_STARTED_GOING_TO_SLEEP = 17;
private static final int SYSTEM_READY = 18;
private static final int CANCEL_KEYGUARD_EXIT_ANIM = 19;
+ private static final int BOOT_INTERACTOR = 20;
/** Enum for reasons behind updating wakeAndUnlock state. */
@Retention(RetentionPolicy.SOURCE)
@@ -1390,6 +1392,7 @@
private final DozeParameters mDozeParameters;
private final SelectedUserInteractor mSelectedUserInteractor;
private final KeyguardInteractor mKeyguardInteractor;
+ private final KeyguardTransitionBootInteractor mTransitionBootInteractor;
@VisibleForTesting
protected FoldGracePeriodProvider mFoldGracePeriodProvider =
new FoldGracePeriodProvider();
@@ -1484,6 +1487,7 @@
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
KeyguardInteractor keyguardInteractor,
+ KeyguardTransitionBootInteractor transitionBootInteractor,
WindowManagerOcclusionManager wmOcclusionManager) {
mContext = context;
mUserTracker = userTracker;
@@ -1524,6 +1528,7 @@
mDozeParameters = dozeParameters;
mSelectedUserInteractor = selectedUserInteractor;
mKeyguardInteractor = keyguardInteractor;
+ mTransitionBootInteractor = transitionBootInteractor;
mStatusBarStateController = statusBarStateController;
statusBarStateController.addCallback(this);
@@ -1678,6 +1683,8 @@
adjustStatusBarLocked();
mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
+ mHandler.obtainMessage(BOOT_INTERACTOR).sendToTarget();
+
final DreamViewModel dreamViewModel = mDreamViewModel.get();
final CommunalTransitionViewModel communalViewModel =
mCommunalTransitionViewModel.get();
@@ -2705,11 +2712,19 @@
message = "SYSTEM_READY";
handleSystemReady();
break;
+ case BOOT_INTERACTOR:
+ message = "BOOT_INTERACTOR";
+ handleBootInteractor();
+ break;
}
Log.d(TAG, "KeyguardViewMediator queue processing message: " + message);
}
};
+ private void handleBootInteractor() {
+ mTransitionBootInteractor.start();
+ }
+
private void tryKeyguardDone() {
if (DEBUG) {
Log.d(TAG, "tryKeyguardDone: pending - " + mKeyguardDonePending + ", animRan - "
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 8a3d017..d0a40ec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -59,6 +59,7 @@
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
@@ -175,6 +176,7 @@
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
KeyguardInteractor keyguardInteractor,
+ KeyguardTransitionBootInteractor transitionBootInteractor,
WindowManagerOcclusionManager windowManagerOcclusionManager) {
return new KeyguardViewMediator(
context,
@@ -225,6 +227,7 @@
wmLockscreenVisibilityManager,
selectedUserInteractor,
keyguardInteractor,
+ transitionBootInteractor,
windowManagerOcclusionManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 797a4ec..690ae71 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -23,6 +23,7 @@
import android.annotation.SuppressLint
import android.os.Trace
import android.util.Log
+import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.withContext
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
@@ -95,7 +96,7 @@
* Emits STARTED and FINISHED transition steps to the given state. This is used during boot to
* seed the repository with the appropriate initial state.
*/
- suspend fun emitInitialStepsFromOff(to: KeyguardState)
+ suspend fun emitInitialStepsFromOff(to: KeyguardState, testSetup: Boolean = false)
/**
* Allows manual control of a transition. When calling [startTransition], the consumer must pass
@@ -108,16 +109,14 @@
suspend fun updateTransition(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
- state: TransitionState
+ state: TransitionState,
)
}
@SysUISingleton
class KeyguardTransitionRepositoryImpl
@Inject
-constructor(
- @Main val mainDispatcher: CoroutineDispatcher,
-) : KeyguardTransitionRepository {
+constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionRepository {
/**
* Each transition between [KeyguardState]s will have an associated Flow. In order to collect
* these events, clients should call [transition].
@@ -140,7 +139,7 @@
ownerName = "",
from = KeyguardState.OFF,
to = KeyguardState.OFF,
- animator = null
+ animator = null,
)
)
override var currentTransitionInfoInternal = _currentTransitionInfo.asStateFlow()
@@ -159,12 +158,7 @@
// to either GONE or LOCKSCREEN once we're booted up and can determine which state we should
// start in.
emitTransition(
- TransitionStep(
- KeyguardState.OFF,
- KeyguardState.OFF,
- 1f,
- TransitionState.FINISHED,
- )
+ TransitionStep(KeyguardState.OFF, KeyguardState.OFF, 1f, TransitionState.FINISHED)
)
}
@@ -217,7 +211,7 @@
TransitionStep(
info,
(animation.animatedValue as Float),
- TransitionState.RUNNING
+ TransitionState.RUNNING,
)
)
}
@@ -266,7 +260,7 @@
override suspend fun updateTransition(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
- state: TransitionState
+ state: TransitionState,
) {
// There is no fairness guarantee with 'withContext', which means that transitions could
// be processed out of order. Use a Mutex to guarantee ordering. [startTransition]
@@ -282,7 +276,7 @@
private suspend fun updateTransitionInternal(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
- state: TransitionState
+ state: TransitionState,
) {
if (updateTransitionId != transitionId) {
Log.e(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
@@ -303,34 +297,51 @@
lastStep = nextStep
}
- override suspend fun emitInitialStepsFromOff(to: KeyguardState) {
- _currentTransitionInfo.value =
- TransitionInfo(
- ownerName = "KeyguardTransitionRepository(boot)",
- from = KeyguardState.OFF,
- to = to,
- animator = null
+ override suspend fun emitInitialStepsFromOff(to: KeyguardState, testSetup: Boolean) {
+ val ownerName = "KeyguardTransitionRepository(boot)"
+ // Tests runs on testDispatcher, which is not the main thread, causing the animator thread
+ // check to fail
+ if (testSetup) {
+ _currentTransitionInfo.value =
+ TransitionInfo(
+ ownerName = ownerName,
+ from = KeyguardState.OFF,
+ to = to,
+ animator = null,
+ )
+ emitTransition(
+ TransitionStep(
+ KeyguardState.OFF,
+ to,
+ 0f,
+ TransitionState.STARTED,
+ ownerName = ownerName,
+ )
)
- emitTransition(
- TransitionStep(
- KeyguardState.OFF,
- to,
- 0f,
- TransitionState.STARTED,
- ownerName = "KeyguardTransitionRepository(boot)",
+ emitTransition(
+ TransitionStep(
+ KeyguardState.OFF,
+ to,
+ 1f,
+ TransitionState.FINISHED,
+ ownerName = ownerName,
+ )
)
- )
-
- emitTransition(
- TransitionStep(
- KeyguardState.OFF,
- to,
- 1f,
- TransitionState.FINISHED,
- ownerName = "KeyguardTransitionRepository(boot)",
- ),
- )
+ } else {
+ startTransition(
+ TransitionInfo(
+ ownerName = ownerName,
+ from = KeyguardState.OFF,
+ to = to,
+ animator =
+ ValueAnimator().apply {
+ interpolator = Interpolators.LINEAR
+ duration = 933L
+ },
+ )
+ )
+ }
}
private fun logAndTrace(step: TransitionStep, isManual: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 0343786..840bc0f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -106,7 +106,7 @@
startTransitionToLockscreenOrHub(
isIdleOnCommunal,
showCommunalFromOccluded,
- dreamFromOccluded
+ dreamFromOccluded,
)
}
}
@@ -127,7 +127,7 @@
startTransitionToLockscreenOrHub(
isIdleOnCommunal,
showCommunalFromOccluded,
- dreamFromOccluded
+ dreamFromOccluded,
)
}
}
@@ -147,7 +147,7 @@
communalSceneInteractor.changeScene(
newScene = CommunalScenes.Communal,
loggingReason = "occluded to hub",
- transitionKey = CommunalTransitionKeys.SimpleFade
+ transitionKey = CommunalTransitionKeys.SimpleFade,
)
} else {
startTransitionTo(KeyguardState.GLANCEABLE_HUB)
@@ -210,8 +210,9 @@
duration =
when (toState) {
- KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+ KeyguardState.ALTERNATE_BOUNCER -> TO_ALTERNATE_BOUNCER_DURATION
KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
+ KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -220,9 +221,10 @@
companion object {
const val TAG = "FromOccludedTransitionInteractor"
private val DEFAULT_DURATION = 500.milliseconds
- val TO_LOCKSCREEN_DURATION = 933.milliseconds
- val TO_GLANCEABLE_HUB_DURATION = 250.milliseconds
+ val TO_ALTERNATE_BOUNCER_DURATION = DEFAULT_DURATION
val TO_AOD_DURATION = DEFAULT_DURATION
val TO_DOZING_DURATION = DEFAULT_DURATION
+ val TO_GLANCEABLE_HUB_DURATION = 250.milliseconds
+ val TO_LOCKSCREEN_DURATION = 933.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index 18b1495..258232b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.power.domain.interactor.PowerInteractor
+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.shade.domain.interactor.ShadeInteractor
@@ -64,6 +65,7 @@
alternateBouncerInteractor: AlternateBouncerInteractor,
shadeInteractor: Lazy<ShadeInteractor>,
keyguardInteractor: Lazy<KeyguardInteractor>,
+ sceneInteractor: Lazy<SceneInteractor>,
) {
val dismissAction: Flow<DismissAction> = repository.dismissAction
@@ -125,7 +127,20 @@
val executeDismissAction: Flow<() -> KeyguardDone> =
merge(
- finishedTransitionToGone,
+ if (SceneContainerFlag.isEnabled) {
+ // Using currentScene instead of finishedTransitionToGone because of a race
+ // condition that forms between finishedTransitionToGone and
+ // isOnShadeWhileUnlocked where the latter emits false before the former emits
+ // true, causing the merge to not emit until it's too late.
+ sceneInteractor
+ .get()
+ .currentScene
+ .map { it == Scenes.Gone }
+ .distinctUntilChanged()
+ .filter { it }
+ } else {
+ finishedTransitionToGone
+ },
isOnShadeWhileUnlocked.filter { it }.map {},
dismissInteractor.dismissKeyguardRequestWithImmediateDismissAction,
)
@@ -135,10 +150,24 @@
val resetDismissAction: Flow<Unit> =
combine(
- transitionInteractor.isFinishedIn(
- scene = Scenes.Gone,
- stateWithoutSceneContainer = GONE,
- ),
+ if (SceneContainerFlag.isEnabled) {
+ // Using currentScene instead of isFinishedIn because of a race condition that
+ // forms between isFinishedIn(Gone) and isOnShadeWhileUnlocked where the latter
+ // emits false before the former emits true, causing the evaluation of the
+ // combine to come up with true, temporarily, before settling on false, which is
+ // a valid final state. That causes an incorrect reset of the dismiss action to
+ // occur before it gets executed.
+ sceneInteractor
+ .get()
+ .currentScene
+ .map { it == Scenes.Gone }
+ .distinctUntilChanged()
+ } else {
+ transitionInteractor.isFinishedIn(
+ scene = Scenes.Gone,
+ stateWithoutSceneContainer = GONE,
+ )
+ },
transitionInteractor.isFinishedIn(
scene = Scenes.Bouncer,
stateWithoutSceneContainer = PRIMARY_BOUNCER,
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 b218300..89f636d 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
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.domain.interactor
import android.util.Log
-import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -46,7 +45,7 @@
val keyguardTransitionInteractor: KeyguardTransitionInteractor,
val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
val repository: KeyguardTransitionRepository,
-) : CoreStartable {
+) {
/**
* Whether the lockscreen should be showing when the device starts up for the first time. If not
@@ -60,14 +59,14 @@
}
}
- override fun start() {
+ fun start() {
scope.launch {
if (internalTransitionInteractor.currentTransitionInfoInternal.value.from != OFF) {
Log.e(
"KeyguardTransitionInteractor",
"showLockscreenOnBoot emitted, but we've already " +
"transitioned to a state other than OFF. We'll respect that " +
- "transition, but this should not happen."
+ "transition, but this should not happen.",
)
} else {
if (SceneContainerFlag.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index 25b8fd3..b715333 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -27,7 +27,6 @@
constructor(
private val interactors: Set<TransitionInteractor>,
private val auditLogger: KeyguardTransitionAuditLogger,
- private val bootInteractor: KeyguardTransitionBootInteractor,
private val statusBarDisableFlagsInteractor: StatusBarDisableFlagsInteractor,
private val keyguardStateCallbackInteractor: KeyguardStateCallbackInteractor,
) : CoreStartable {
@@ -54,7 +53,6 @@
it.start()
}
auditLogger.start()
- bootInteractor.start()
statusBarDisableFlagsInteractor.start()
keyguardStateCallbackInteractor.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index f1b9cba..00aa44f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -47,56 +47,53 @@
constraintLayout.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch("$TAG#viewModel.blueprint") {
- viewModel.blueprint
- .pairwise(
- null as KeyguardBlueprint?,
- )
- .collect { (prevBlueprint, blueprint) ->
- val config = Config.DEFAULT
- val transition =
- if (
- !KeyguardBottomAreaRefactor.isEnabled &&
- prevBlueprint != null &&
- prevBlueprint != blueprint
- ) {
- BaseBlueprintTransition(clockViewModel)
- .addTransition(
- IntraBlueprintTransition(
- config,
- clockViewModel,
- smartspaceViewModel
- )
+ viewModel.blueprint.pairwise(null as KeyguardBlueprint?).collect {
+ (prevBlueprint, blueprint) ->
+ val config = Config.DEFAULT
+ val transition =
+ if (
+ !KeyguardBottomAreaRefactor.isEnabled &&
+ prevBlueprint != null &&
+ prevBlueprint != blueprint
+ ) {
+ BaseBlueprintTransition(clockViewModel)
+ .addTransition(
+ IntraBlueprintTransition(
+ config,
+ clockViewModel,
+ smartspaceViewModel,
)
- } else {
- IntraBlueprintTransition(
- config,
- clockViewModel,
- smartspaceViewModel
)
+ } else {
+ IntraBlueprintTransition(
+ config,
+ clockViewModel,
+ smartspaceViewModel,
+ )
+ }
+
+ viewModel.runTransition(constraintLayout, transition, config) {
+ // Replace sections from the previous blueprint with the new ones
+ blueprint.replaceViews(
+ constraintLayout,
+ prevBlueprint,
+ config.rebuildSections,
+ )
+
+ val cs =
+ ConstraintSet().apply {
+ clone(constraintLayout)
+ val emptyLayout = ConstraintSet.Layout()
+ knownIds.forEach {
+ getConstraint(it).layout.copyFrom(emptyLayout)
+ }
+ blueprint.applyConstraints(this)
}
- viewModel.runTransition(constraintLayout, transition, config) {
- // Replace sections from the previous blueprint with the new ones
- blueprint.replaceViews(
- constraintLayout,
- prevBlueprint,
- config.rebuildSections
- )
-
- val cs =
- ConstraintSet().apply {
- clone(constraintLayout)
- val emptyLayout = ConstraintSet.Layout()
- knownIds.forEach {
- getConstraint(it).layout.copyFrom(emptyLayout)
- }
- blueprint.applyConstraints(this)
- }
-
- logAlphaVisibilityScaleOfAppliedConstraintSet(cs, clockViewModel)
- cs.applyTo(constraintLayout)
- }
+ logAlphaVisibilityScaleOfAppliedConstraintSet(cs, clockViewModel)
+ cs.applyTo(constraintLayout)
}
+ }
}
launch("$TAG#viewModel.refreshTransition") {
@@ -105,7 +102,8 @@
viewModel.runTransition(
constraintLayout,
- IntraBlueprintTransition(config, clockViewModel, smartspaceViewModel),
+ clockViewModel,
+ smartspaceViewModel,
config,
) {
blueprint.rebuildViews(constraintLayout, config.rebuildSections)
@@ -126,7 +124,7 @@
private fun logAlphaVisibilityScaleOfAppliedConstraintSet(
cs: ConstraintSet,
- viewModel: KeyguardClockViewModel
+ viewModel: KeyguardClockViewModel,
) {
val currentClock = viewModel.currentClock.value
if (!DEBUG || currentClock == null) return
@@ -137,19 +135,19 @@
TAG,
"applyCsToSmallClock: vis=${cs.getVisibility(smallClockViewId)} " +
"alpha=${cs.getConstraint(smallClockViewId).propertySet.alpha} " +
- "scale=${cs.getConstraint(smallClockViewId).transform.scaleX} "
+ "scale=${cs.getConstraint(smallClockViewId).transform.scaleX} ",
)
Log.i(
TAG,
"applyCsToLargeClock: vis=${cs.getVisibility(largeClockViewId)} " +
"alpha=${cs.getConstraint(largeClockViewId).propertySet.alpha} " +
"scale=${cs.getConstraint(largeClockViewId).transform.scaleX} " +
- "pivotX=${cs.getConstraint(largeClockViewId).transform.transformPivotX} "
+ "pivotX=${cs.getConstraint(largeClockViewId).transform.transformPivotX} ",
)
Log.i(
TAG,
"applyCsToSmartspaceDate: vis=${cs.getVisibility(smartspaceDateId)} " +
- "alpha=${cs.getConstraint(smartspaceDateId).propertySet.alpha}"
+ "alpha=${cs.getConstraint(smartspaceDateId).propertySet.alpha}",
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
index aa0a9d9..9a55f7b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
@@ -29,18 +29,18 @@
smartspaceViewModel: KeyguardSmartspaceViewModel,
) : TransitionSet() {
- enum class Type(
- val priority: Int,
- val animateNotifChanges: Boolean,
- ) {
+ enum class Type(val priority: Int, val animateNotifChanges: Boolean) {
ClockSize(100, true),
ClockCenter(99, false),
DefaultClockStepping(98, false),
- SmartspaceVisibility(2, true),
- DefaultTransition(1, false),
+ SmartspaceVisibility(3, true),
+ DefaultTransition(2, false),
// When transition between blueprint, we don't need any duration or interpolator but we need
// all elements go to correct state
- NoTransition(0, false),
+ NoTransition(1, false),
+ // Similar to NoTransition, except also does not explicitly update any alpha. Used in
+ // OFF->LOCKSCREEN transition
+ Init(0, false),
}
data class Config(
@@ -57,6 +57,7 @@
init {
ordering = ORDERING_TOGETHER
when (config.type) {
+ Type.Init -> {}
Type.NoTransition -> {}
Type.DefaultClockStepping ->
addTransition(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index ff84826..a1c963b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -53,14 +53,11 @@
internal fun ConstraintSet.setVisibility(views: Iterable<View>, visibility: Int) =
views.forEach { view -> this.setVisibility(view.id, visibility) }
-internal fun ConstraintSet.setAlpha(views: Iterable<View>, alpha: Float) =
- views.forEach { view -> this.setAlpha(view.id, alpha) }
+internal fun ConstraintSet.setScaleX(views: Iterable<View>, scaleX: Float) =
+ views.forEach { view -> this.setScaleX(view.id, scaleX) }
-internal fun ConstraintSet.setScaleX(views: Iterable<View>, alpha: Float) =
- views.forEach { view -> this.setScaleX(view.id, alpha) }
-
-internal fun ConstraintSet.setScaleY(views: Iterable<View>, alpha: Float) =
- views.forEach { view -> this.setScaleY(view.id, alpha) }
+internal fun ConstraintSet.setScaleY(views: Iterable<View>, scaleY: Float) =
+ views.forEach { view -> this.setScaleY(view.id, scaleY) }
@SysUISingleton
class ClockSection
@@ -126,8 +123,6 @@
return constraintSet.apply {
setVisibility(getTargetClockFace(clock).views, VISIBLE)
setVisibility(getNonTargetClockFace(clock).views, GONE)
- setAlpha(getTargetClockFace(clock).views, 1F)
- setAlpha(getNonTargetClockFace(clock).views, 0F)
if (!keyguardClockViewModel.isLargeClockVisible.value) {
connect(sharedR.id.bc_smartspace_view, TOP, sharedR.id.date_smartspace_view, BOTTOM)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt
index 3f2ef29..c49e783 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt
@@ -28,22 +28,22 @@
import kotlinx.coroutines.flow.Flow
/**
- * Breaks down ALTERNATE_BOUNCER->GONE transition into discrete steps for corresponding views to
+ * Breaks down ALTERNATE_BOUNCER->OCCLUDED transition into discrete steps for corresponding views to
* consume.
*/
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class AlternateBouncerToOccludedTransitionViewModel
@Inject
-constructor(
- animationFlow: KeyguardTransitionAnimationFlow,
-) : DeviceEntryIconTransition {
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = TO_OCCLUDED_DURATION,
edge = Edge.create(from = ALTERNATE_BOUNCER, to = OCCLUDED),
)
+ val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f)
+
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index a021de4..ca1a800 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -56,6 +56,7 @@
occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
occludedToDozingTransitionViewModel: OccludedToDozingTransitionViewModel,
occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel,
primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel,
primaryBouncerToDozingTransitionViewModel: PrimaryBouncerToDozingTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
@@ -67,14 +68,14 @@
.map {
Utils.getColorAttrDefaultColor(
context,
- com.android.internal.R.attr.colorSurface
+ com.android.internal.R.attr.colorSurface,
)
}
.onStart {
emit(
Utils.getColorAttrDefaultColor(
context,
- com.android.internal.R.attr.colorSurface
+ com.android.internal.R.attr.colorSurface,
)
)
}
@@ -86,23 +87,23 @@
deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBackground ->
if (useBackground) {
setOf(
- lockscreenToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
- aodToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
- goneToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
- primaryBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
- occludedToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
- occludedToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
- dreamingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
alternateBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
- goneToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
- goneToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
- primaryBouncerToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
- dozingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
alternateBouncerToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ aodToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ dozingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
dreamingToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
- primaryBouncerToLockscreenTransitionViewModel
- .deviceEntryBackgroundViewAlpha,
+ dreamingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ goneToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ goneToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ goneToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ lockscreenToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ occludedToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
occludedToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ occludedToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ offToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ primaryBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ primaryBouncerToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ primaryBouncerToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
)
.merge()
.onStart {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
index 4cf3c4e..1289036 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
@@ -24,6 +24,9 @@
import androidx.constraintlayout.widget.ConstraintLayout
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
import javax.inject.Inject
@@ -37,6 +40,7 @@
constructor(
@Main private val handler: Handler,
private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
val blueprint = keyguardBlueprintInteractor.blueprint
val blueprintId = keyguardBlueprintInteractor.blueprintId
@@ -49,12 +53,12 @@
private val transitionListener =
object : Transition.TransitionListener {
override fun onTransitionCancel(transition: Transition) {
- if (DEBUG) Log.e(TAG, "onTransitionCancel: ${transition::class.simpleName}")
+ if (DEBUG) Log.w(TAG, "onTransitionCancel: ${transition::class.simpleName}")
updateTransitions(null) { remove(transition) }
}
override fun onTransitionEnd(transition: Transition) {
- if (DEBUG) Log.e(TAG, "onTransitionEnd: ${transition::class.simpleName}")
+ if (DEBUG) Log.i(TAG, "onTransitionEnd: ${transition::class.simpleName}")
updateTransitions(null) { remove(transition) }
}
@@ -86,6 +90,28 @@
fun runTransition(
constraintLayout: ConstraintLayout,
+ clockViewModel: KeyguardClockViewModel,
+ smartspaceViewModel: KeyguardSmartspaceViewModel,
+ config: Config,
+ apply: () -> Unit,
+ ) {
+ val newConfig =
+ if (keyguardTransitionInteractor.getCurrentState() == KeyguardState.OFF) {
+ config.copy(type = Type.Init)
+ } else {
+ config
+ }
+
+ runTransition(
+ constraintLayout,
+ IntraBlueprintTransition(newConfig, clockViewModel, smartspaceViewModel),
+ config,
+ apply,
+ )
+ }
+
+ fun runTransition(
+ constraintLayout: ConstraintLayout,
transition: Transition,
config: Config,
apply: () -> Unit,
@@ -103,21 +129,29 @@
return
}
+ // Don't allow transitions with animations while in OFF state
+ val newConfig =
+ if (keyguardTransitionInteractor.getCurrentState() == KeyguardState.OFF) {
+ config.copy(type = Type.Init)
+ } else {
+ config
+ }
+
if (DEBUG) {
Log.i(
TAG,
"runTransition: running ${transition::class.simpleName}: " +
- "currentPriority=$currentPriority; config=$config",
+ "currentPriority=$currentPriority; config=$newConfig",
)
}
// beginDelayedTransition makes a copy, so we temporarially add the uncopied transition to
// the running set until the copy is started by the handler.
- updateTransitions(TransitionData(config)) { add(transition) }
+ updateTransitions(TransitionData(newConfig)) { add(transition) }
transition.addListener(transitionListener)
handler.post {
- if (config.terminatePrevious) {
+ if (newConfig.terminatePrevious) {
TransitionManager.endTransitions(constraintLayout)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 10a2e5c..3705c2c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -20,7 +20,6 @@
import android.graphics.Point
import android.util.MathUtils
import android.view.View.VISIBLE
-import com.android.app.tracing.coroutines.launch
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -35,6 +34,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
@@ -88,6 +88,8 @@
AlternateBouncerToGoneTransitionViewModel,
private val alternateBouncerToLockscreenTransitionViewModel:
AlternateBouncerToLockscreenTransitionViewModel,
+ private val alternateBouncerToOccludedTransitionViewModel:
+ AlternateBouncerToOccludedTransitionViewModel,
private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
@@ -112,9 +114,12 @@
private val lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
private val lockscreenToPrimaryBouncerTransitionViewModel:
LockscreenToPrimaryBouncerTransitionViewModel,
+ private val occludedToAlternateBouncerTransitionViewModel:
+ OccludedToAlternateBouncerTransitionViewModel,
private val occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
private val occludedToDozingTransitionViewModel: OccludedToDozingTransitionViewModel,
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ private val offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel,
private val primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel,
private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
private val primaryBouncerToLockscreenTransitionViewModel:
@@ -201,6 +206,10 @@
notificationShadeWindowModel.isKeyguardOccluded,
communalInteractor.isIdleOnCommunal,
keyguardTransitionInteractor
+ .transitionValue(OFF)
+ .map { it > 1f - offToLockscreenTransitionViewModel.alphaStartAt }
+ .onStart { emit(false) },
+ keyguardTransitionInteractor
.transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
.map { it == 1f }
.onStart { emit(false) },
@@ -227,6 +236,7 @@
alternateBouncerToAodTransitionViewModel.lockscreenAlpha(viewState),
alternateBouncerToGoneTransitionViewModel.lockscreenAlpha(viewState),
alternateBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
+ alternateBouncerToOccludedTransitionViewModel.lockscreenAlpha,
aodToGoneTransitionViewModel.lockscreenAlpha(viewState),
aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
@@ -249,14 +259,16 @@
lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState),
lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
+ occludedToAlternateBouncerTransitionViewModel.lockscreenAlpha,
occludedToAodTransitionViewModel.lockscreenAlpha,
occludedToDozingTransitionViewModel.lockscreenAlpha,
occludedToLockscreenTransitionViewModel.lockscreenAlpha,
+ offToLockscreenTransitionViewModel.lockscreenAlpha,
primaryBouncerToAodTransitionViewModel.lockscreenAlpha,
primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
)
- .onStart { emit(1f) },
+ .onStart { emit(0f) },
) { hideKeyguard, alpha ->
if (hideKeyguard) {
0f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index 8d9ccef..88e8968 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -52,18 +52,26 @@
/** Lockscreen views alpha */
val lockscreenAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- duration = 250.milliseconds,
- onStep = { 1f - it },
- name = "LOCKSCREEN->OCCLUDED: lockscreenAlpha",
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsNotExpanded =
+ transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
+ name = "LOCKSCREEN->OCCLUDED: lockscreenAlpha",
+ ),
+ flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
)
val shortcutsAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- duration = 250.milliseconds,
- onStep = { 1 - it },
- onFinish = { 0f },
- onCancel = { 1f },
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsNotExpanded =
+ transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
+ onFinish = { 0f },
+ onCancel = { 1f },
+ ),
+ flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
)
/** Lockscreen views y-translation */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModel.kt
new file mode 100644
index 0000000..5bfcccb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_ALTERNATE_BOUNCER_DURATION
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down OCCLUDED->ALTERNATE_BOUNCER transition into discrete steps for corresponding views to
+ * consume.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class OccludedToAlternateBouncerTransitionViewModel
+@Inject
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = TO_ALTERNATE_BOUNCER_DURATION,
+ edge = Edge.create(from = OCCLUDED, to = ALTERNATE_BOUNCER),
+ )
+
+ val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f)
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
index 1eecbd5..b4acce6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
@@ -29,23 +30,29 @@
@SysUISingleton
class OffToLockscreenTransitionViewModel
@Inject
-constructor(
- animationFlow: KeyguardTransitionAnimationFlow,
-) : DeviceEntryIconTransition {
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
+
+ private val startTime = 300.milliseconds
+ private val alphaDuration = 633.milliseconds
+ val alphaStartAt = startTime / (alphaDuration + startTime)
private val transitionAnimation =
animationFlow.setup(
- duration = 250.milliseconds,
+ duration = startTime + alphaDuration,
edge = Edge.create(from = OFF, to = LOCKSCREEN),
)
- val shortcutsAlpha: Flow<Float> =
+ val lockscreenAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
- duration = 250.milliseconds,
+ startTime = startTime,
+ duration = alphaDuration,
+ interpolator = EMPHASIZED_ACCELERATE,
onStep = { it },
- onCancel = { 0f },
)
- override val deviceEntryParentViewAlpha: Flow<Float> =
- transitionAnimation.immediatelyTransitionTo(1f)
+ val shortcutsAlpha: Flow<Float> = lockscreenAlpha
+
+ override val deviceEntryParentViewAlpha: Flow<Float> = lockscreenAlpha
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> = lockscreenAlpha
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 84aae65..222d783 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -111,7 +111,7 @@
arrayOf(
MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
MediaMetadata.METADATA_KEY_ART_URI,
- MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+ MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
)
private const val TAG = "MediaDataManager"
@@ -136,7 +136,7 @@
active = true,
resumeAction = null,
instanceId = InstanceId.fakeInstanceId(-1),
- appUid = Process.INVALID_UID
+ appUid = Process.INVALID_UID,
)
internal val EMPTY_SMARTSPACE_MEDIA_DATA =
@@ -163,7 +163,7 @@
Settings.Secure.getInt(
context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1
+ 1,
)
return Utils.useQsMediaPlayer(context) && flag > 0
}
@@ -217,7 +217,7 @@
private val themeText =
com.android.settingslib.Utils.getColorAttr(
context,
- com.android.internal.R.attr.textColorPrimary
+ com.android.internal.R.attr.textColorPrimary,
)
.defaultColor
@@ -387,7 +387,7 @@
uiExecutor,
SmartspaceSession.OnTargetsAvailableListener { targets ->
smartspaceMediaDataProvider.onTargetsAvailable(targets)
- }
+ },
)
}
smartspaceSession?.let { it.requestSmartspaceUpdate() }
@@ -398,12 +398,12 @@
if (!allowMediaRecommendations) {
dismissSmartspaceRecommendation(
key = smartspaceMediaData.targetId,
- delay = 0L
+ delay = 0L,
)
}
}
},
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION
+ Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
)
}
@@ -461,7 +461,7 @@
token: MediaSession.Token,
appName: String,
appIntent: PendingIntent,
- packageName: String
+ packageName: String,
) {
// Resume controls don't have a notification key, so store by package name instead
if (!mediaEntries.containsKey(packageName)) {
@@ -497,7 +497,7 @@
token,
appName,
appIntent,
- packageName
+ packageName,
)
}
} else {
@@ -509,7 +509,7 @@
token,
appName,
appIntent,
- packageName
+ packageName,
)
}
}
@@ -609,14 +609,14 @@
result.appUid,
sbn.packageName,
instanceId,
- result.playbackLocation
+ result.playbackLocation,
)
} else if (result.playbackLocation != currentEntry?.playbackLocation) {
logger.logPlaybackLocationChange(
result.appUid,
sbn.packageName,
instanceId,
- result.playbackLocation
+ result.playbackLocation,
)
}
@@ -722,30 +722,32 @@
/** Called when the player's [PlaybackState] has been updated with new actions and/or state */
private fun updateState(key: String, state: PlaybackState) {
mediaEntries.get(key)?.let {
- val token = it.token
- if (token == null) {
- if (DEBUG) Log.d(TAG, "State updated, but token was null")
- return
- }
- val actions =
- createActionsFromState(
- it.packageName,
- mediaControllerFactory.create(it.token),
- UserHandle(it.userId)
- )
-
- // Control buttons
- // If flag is enabled and controller has a PlaybackState,
- // create actions from session info
- // otherwise, no need to update semantic actions.
- val data =
- if (actions != null) {
- it.copy(semanticActions = actions, isPlaying = isPlayingState(state.state))
- } else {
- it.copy(isPlaying = isPlayingState(state.state))
+ backgroundExecutor.execute {
+ val token = it.token
+ if (token == null) {
+ if (DEBUG) Log.d(TAG, "State updated, but token was null")
+ return@execute
}
- if (DEBUG) Log.d(TAG, "State updated outside of notification")
- onMediaDataLoaded(key, key, data)
+ val actions =
+ createActionsFromState(
+ it.packageName,
+ mediaControllerFactory.create(it.token),
+ UserHandle(it.userId),
+ )
+
+ // Control buttons
+ // If flag is enabled and controller has a PlaybackState,
+ // create actions from session info
+ // otherwise, no need to update semantic actions.
+ val data =
+ if (actions != null) {
+ it.copy(semanticActions = actions, isPlaying = isPlayingState(state.state))
+ } else {
+ it.copy(isPlaying = isPlayingState(state.state))
+ }
+ if (DEBUG) Log.d(TAG, "State updated outside of notification")
+ foregroundExecutor.execute { onMediaDataLoaded(key, key, data) }
+ }
}
}
@@ -773,7 +775,7 @@
}
foregroundExecutor.executeDelayed(
{ removeEntry(key = key, userInitiated = userInitiated) },
- delay
+ delay,
)
return existed
}
@@ -793,12 +795,12 @@
smartspaceMediaData =
EMPTY_SMARTSPACE_MEDIA_DATA.copy(
targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId
+ instanceId = smartspaceMediaData.instanceId,
)
}
foregroundExecutor.executeDelayed(
{ notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = true) },
- delay
+ delay,
)
}
@@ -826,7 +828,7 @@
token: MediaSession.Token,
appName: String,
appIntent: PendingIntent,
- packageName: String
+ packageName: String,
) =
withContext(backgroundDispatcher) {
val lastActive = systemClock.elapsedRealtime()
@@ -843,7 +845,7 @@
token,
appName,
appIntent,
- packageName
+ packageName,
)
if (result == null || desc.title.isNullOrBlank()) {
Log.d(TAG, "No MediaData result for resumption")
@@ -882,7 +884,7 @@
appUid = result.appUid,
isExplicit = result.isExplicit,
resumeProgress = result.resumeProgress,
- )
+ ),
)
}
}
@@ -895,7 +897,7 @@
token: MediaSession.Token,
appName: String,
appIntent: PendingIntent,
- packageName: String
+ packageName: String,
) {
if (desc.title.isNullOrBlank()) {
Log.e(TAG, "Description incomplete")
@@ -966,7 +968,7 @@
appUid = appUid,
isExplicit = isExplicit,
resumeProgress = progress,
- )
+ ),
)
}
}
@@ -981,7 +983,7 @@
val token =
sbn.notification.extras.getParcelable(
Notification.EXTRA_MEDIA_SESSION,
- MediaSession.Token::class.java
+ MediaSession.Token::class.java,
)
if (token == null) {
return
@@ -993,7 +995,7 @@
val appInfo =
notif.extras.getParcelable(
Notification.EXTRA_BUILDER_APPLICATION_INFO,
- ApplicationInfo::class.java
+ ApplicationInfo::class.java,
) ?: getAppInfoFromPackage(sbn.packageName)
// App name
@@ -1057,7 +1059,7 @@
val deviceIntent =
extras.getParcelable(
Notification.EXTRA_MEDIA_REMOTE_INTENT,
- PendingIntent::class.java
+ PendingIntent::class.java,
)
Log.d(TAG, "$key is RCN for $deviceName")
@@ -1073,7 +1075,7 @@
deviceDrawable,
deviceName,
deviceIntent,
- showBroadcastButton = false
+ showBroadcastButton = false,
)
}
}
@@ -1160,7 +1162,7 @@
mediaData.copy(
resumeAction = oldResumeAction,
hasCheckedForResume = oldHasCheckedForResume,
- active = oldActive
+ active = oldActive,
)
onMediaDataLoaded(key, oldKey, mediaData)
}
@@ -1169,7 +1171,7 @@
private fun logSingleVsMultipleMediaAdded(
appUid: Int,
packageName: String,
- instanceId: InstanceId
+ instanceId: InstanceId,
) {
if (mediaEntries.size == 1) {
logger.logSingleMediaPlayerInCarousel(appUid, packageName, instanceId)
@@ -1207,7 +1209,7 @@
private fun createActionsFromState(
packageName: String,
controller: MediaController,
- user: UserHandle
+ user: UserHandle,
): MediaButton? {
if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
return null
@@ -1245,7 +1247,7 @@
packageName,
ContentProvider.getUriWithoutUserId(uri),
Intent.FLAG_GRANT_READ_URI_PERMISSION,
- ContentProvider.getUserIdFromUri(uri, userId)
+ ContentProvider.getUserIdFromUri(uri, userId),
)
return loadBitmapFromUri(uri)
} catch (e: SecurityException) {
@@ -1282,7 +1284,7 @@
val scale =
MediaDataUtils.getScaleFactor(
APair(width, height),
- APair(artworkWidth, artworkHeight)
+ APair(artworkWidth, artworkHeight),
)
// Downscale if needed
@@ -1307,7 +1309,7 @@
.loadDrawable(context),
action,
context.getString(R.string.controls_media_resume),
- context.getDrawable(R.drawable.ic_media_play_container)
+ context.getDrawable(R.drawable.ic_media_play_container),
)
}
@@ -1371,10 +1373,7 @@
// There should NOT be more than 1 Smartspace media update. When it happens, it
// indicates a bad state or an error. Reset the status accordingly.
Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
- notifySmartspaceMediaDataRemoved(
- smartspaceMediaData.targetId,
- immediately = false,
- )
+ notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
}
}
@@ -1420,7 +1419,7 @@
private fun handlePossibleRemoval(
key: String,
removed: MediaData,
- notificationRemoved: Boolean = false
+ notificationRemoved: Boolean = false,
) {
val hasSession = removed.token != null
if (hasSession && removed.semanticActions != null) {
@@ -1445,7 +1444,7 @@
Log.d(
TAG,
"Notification ($notificationRemoved) and/or session " +
- "($hasSession) gone for inactive player $key"
+ "($hasSession) gone for inactive player $key",
)
}
convertToResumePlayer(key, removed)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
index f2825d0..4f97913 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.controls.domain.pipeline
+import android.annotation.WorkerThread
import android.app.ActivityOptions
import android.app.BroadcastOptions
import android.app.Notification
@@ -50,6 +51,7 @@
* @return a Pair consisting of a list of media actions, and a list of ints representing which of
* those actions should be shown in the compact player
*/
+@WorkerThread
fun createActionsFromState(
context: Context,
packageName: String,
@@ -69,7 +71,7 @@
context.getString(R.string.controls_media_button_connecting),
context.getDrawable(R.drawable.ic_media_connecting_container),
// Specify a rebind id to prevent the spinner from restarting on later binds.
- com.android.internal.R.drawable.progress_small_material
+ com.android.internal.R.drawable.progress_small_material,
)
} else if (isPlayingState(state.state)) {
getStandardAction(context, controller, state.actions, PlaybackState.ACTION_PAUSE)
@@ -128,7 +130,7 @@
nextCustomAction(),
nextCustomAction(),
reserveNext,
- reservePrev
+ reservePrev,
)
}
@@ -146,7 +148,7 @@
context: Context,
controller: MediaController,
stateActions: Long,
- @PlaybackState.Actions action: Long
+ @PlaybackState.Actions action: Long,
): MediaAction? {
if (!includesAction(stateActions, action)) {
return null
@@ -158,7 +160,7 @@
context.getDrawable(R.drawable.ic_media_play),
{ controller.transportControls.play() },
context.getString(R.string.controls_media_button_play),
- context.getDrawable(R.drawable.ic_media_play_container)
+ context.getDrawable(R.drawable.ic_media_play_container),
)
}
PlaybackState.ACTION_PAUSE -> {
@@ -166,7 +168,7 @@
context.getDrawable(R.drawable.ic_media_pause),
{ controller.transportControls.pause() },
context.getString(R.string.controls_media_button_pause),
- context.getDrawable(R.drawable.ic_media_pause_container)
+ context.getDrawable(R.drawable.ic_media_pause_container),
)
}
PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
@@ -174,7 +176,7 @@
MediaControlDrawables.getPrevIcon(context),
{ controller.transportControls.skipToPrevious() },
context.getString(R.string.controls_media_button_prev),
- null
+ null,
)
}
PlaybackState.ACTION_SKIP_TO_NEXT -> {
@@ -182,7 +184,7 @@
MediaControlDrawables.getNextIcon(context),
{ controller.transportControls.skipToNext() },
context.getString(R.string.controls_media_button_next),
- null
+ null,
)
}
else -> null
@@ -194,13 +196,13 @@
context: Context,
packageName: String,
controller: MediaController,
- customAction: PlaybackState.CustomAction
+ customAction: PlaybackState.CustomAction,
): MediaAction {
return MediaAction(
Icon.createWithResource(packageName, customAction.icon).loadDrawable(context),
{ controller.transportControls.sendCustomAction(customAction, customAction.extras) },
customAction.name,
- null
+ null,
)
}
@@ -218,7 +220,7 @@
/** Generate action buttons based on notification actions */
fun createActionsFromNotification(
context: Context,
- sbn: StatusBarNotification
+ sbn: StatusBarNotification,
): Pair<List<MediaNotificationAction>, List<Int>> {
val notif = sbn.notification
val actionIcons: MutableList<MediaNotificationAction> = ArrayList()
@@ -229,7 +231,7 @@
if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
Log.e(
TAG,
- "Too many compact actions for ${sbn.key}, limiting to first $MAX_COMPACT_ACTIONS"
+ "Too many compact actions for ${sbn.key}, limiting to first $MAX_COMPACT_ACTIONS",
)
actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
}
@@ -239,7 +241,7 @@
Log.w(
TAG,
"Too many notification actions for ${sbn.key}, " +
- "limiting to first $MAX_NOTIFICATION_ACTIONS"
+ "limiting to first $MAX_NOTIFICATION_ACTIONS",
)
}
@@ -253,7 +255,7 @@
val themeText =
com.android.settingslib.Utils.getColorAttr(
context,
- com.android.internal.R.attr.textColorPrimary
+ com.android.internal.R.attr.textColorPrimary,
)
.defaultColor
@@ -271,7 +273,7 @@
action.isAuthenticationRequired,
action.actionIntent,
mediaActionIcon,
- action.title
+ action.title,
)
actionIcons.add(mediaAction)
}
@@ -288,7 +290,7 @@
*/
fun getNotificationActions(
actions: List<MediaNotificationAction>,
- activityStarter: ActivityStarter
+ activityStarter: ActivityStarter,
): List<MediaAction> {
return actions.map { action ->
val runnable =
@@ -303,7 +305,7 @@
activityStarter.dismissKeyguardThenExecute(
{ sendPendingIntent(action.actionIntent) },
{},
- true
+ true,
)
else -> sendPendingIntent(actionIntent)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index 5f0a9f8..fd7b6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -119,7 +119,7 @@
arrayOf(
MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
MediaMetadata.METADATA_KEY_ART_URI,
- MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+ MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
)
private const val TAG = "MediaDataProcessor"
@@ -177,7 +177,7 @@
private val themeText =
com.android.settingslib.Utils.getColorAttr(
context,
- com.android.internal.R.attr.textColorPrimary
+ com.android.internal.R.attr.textColorPrimary,
)
.defaultColor
@@ -365,7 +365,7 @@
secureSettings.getBoolForUser(
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
true,
- UserHandle.USER_CURRENT
+ UserHandle.USER_CURRENT,
)
useQsMediaPlayer && flag
@@ -386,7 +386,7 @@
if (!allowMediaRecommendations) {
dismissSmartspaceRecommendation(
key = mediaDataRepository.smartspaceMediaData.value.targetId,
- delay = 0L
+ delay = 0L,
)
}
}
@@ -413,7 +413,7 @@
token: MediaSession.Token,
appName: String,
appIntent: PendingIntent,
- packageName: String
+ packageName: String,
) {
// Resume controls don't have a notification key, so store by package name instead
if (!mediaDataRepository.mediaEntries.value.containsKey(packageName)) {
@@ -449,7 +449,7 @@
token,
appName,
appIntent,
- packageName
+ packageName,
)
}
} else {
@@ -461,7 +461,7 @@
token,
appName,
appIntent,
- packageName
+ packageName,
)
}
}
@@ -582,30 +582,37 @@
/** Called when the player's [PlaybackState] has been updated with new actions and/or state */
internal fun updateState(key: String, state: PlaybackState) {
mediaDataRepository.mediaEntries.value.get(key)?.let {
- val token = it.token
- if (token == null) {
- if (DEBUG) Log.d(TAG, "State updated, but token was null")
- return
- }
- val actions =
- createActionsFromState(
- it.packageName,
- mediaControllerFactory.create(it.token),
- UserHandle(it.userId)
- )
+ applicationScope.launch {
+ withContext(backgroundDispatcher) {
+ val token = it.token
+ if (token == null) {
+ if (DEBUG) Log.d(TAG, "State updated, but token was null")
+ return@withContext
+ }
+ val actions =
+ createActionsFromState(
+ it.packageName,
+ mediaControllerFactory.create(it.token),
+ UserHandle(it.userId),
+ )
- // Control buttons
- // If flag is enabled and controller has a PlaybackState,
- // create actions from session info
- // otherwise, no need to update semantic actions.
- val data =
- if (actions != null) {
- it.copy(semanticActions = actions, isPlaying = isPlayingState(state.state))
- } else {
- it.copy(isPlaying = isPlayingState(state.state))
+ // Control buttons
+ // If flag is enabled and controller has a PlaybackState,
+ // create actions from session info
+ // otherwise, no need to update semantic actions.
+ val data =
+ if (actions != null) {
+ it.copy(
+ semanticActions = actions,
+ isPlaying = isPlayingState(state.state),
+ )
+ } else {
+ it.copy(isPlaying = isPlayingState(state.state))
+ }
+ if (DEBUG) Log.d(TAG, "State updated outside of notification")
+ withContext(mainDispatcher) { onMediaDataLoaded(key, key, data) }
}
- if (DEBUG) Log.d(TAG, "State updated outside of notification")
- onMediaDataLoaded(key, key, data)
+ }
}
}
@@ -633,7 +640,7 @@
}
foregroundExecutor.executeDelayed(
{ removeEntry(key, userInitiated = userInitiated) },
- delayMs
+ delayMs,
)
return existed
}
@@ -657,7 +664,7 @@
if (mediaDataRepository.dismissSmartspaceRecommendation(key)) {
foregroundExecutor.executeDelayed(
{ notifySmartspaceMediaDataRemoved(key, immediately = true) },
- delay
+ delay,
)
}
}
@@ -677,7 +684,7 @@
token: MediaSession.Token,
appName: String,
appIntent: PendingIntent,
- packageName: String
+ packageName: String,
) =
withContext(backgroundDispatcher) {
val lastActive = systemClock.elapsedRealtime()
@@ -694,7 +701,7 @@
token,
appName,
appIntent,
- packageName
+ packageName,
)
if (result == null || desc.title.isNullOrBlank()) {
Log.d(TAG, "No MediaData result for resumption")
@@ -733,7 +740,7 @@
appUid = result.appUid,
isExplicit = result.isExplicit,
resumeProgress = result.resumeProgress,
- )
+ ),
)
}
}
@@ -746,7 +753,7 @@
token: MediaSession.Token,
appName: String,
appIntent: PendingIntent,
- packageName: String
+ packageName: String,
) {
if (desc.title.isNullOrBlank()) {
Log.e(TAG, "Description incomplete")
@@ -818,7 +825,7 @@
isExplicit = isExplicit,
resumeProgress = progress,
smartspaceId = SmallHash.hash(appUid + systemClock.currentTimeMillis().toInt()),
- )
+ ),
)
}
}
@@ -887,14 +894,14 @@
result.appUid,
sbn.packageName,
instanceId,
- result.playbackLocation
+ result.playbackLocation,
)
} else if (result.playbackLocation != oldEntry?.playbackLocation) {
logger.logPlaybackLocationChange(
result.appUid,
sbn.packageName,
instanceId,
- result.playbackLocation
+ result.playbackLocation,
)
}
@@ -911,7 +918,7 @@
val token =
sbn.notification.extras.getParcelable(
Notification.EXTRA_MEDIA_SESSION,
- MediaSession.Token::class.java
+ MediaSession.Token::class.java,
)
if (token == null) {
return
@@ -923,7 +930,7 @@
val appInfo =
notif.extras.getParcelable(
Notification.EXTRA_BUILDER_APPLICATION_INFO,
- ApplicationInfo::class.java
+ ApplicationInfo::class.java,
) ?: getAppInfoFromPackage(sbn.packageName)
// App name
@@ -987,7 +994,7 @@
val deviceIntent =
extras.getParcelable(
Notification.EXTRA_MEDIA_REMOTE_INTENT,
- PendingIntent::class.java
+ PendingIntent::class.java,
)
Log.d(TAG, "$key is RCN for $deviceName")
@@ -1003,7 +1010,7 @@
deviceDrawable,
deviceName,
deviceIntent,
- showBroadcastButton = false
+ showBroadcastButton = false,
)
}
}
@@ -1093,7 +1100,7 @@
mediaData.copy(
resumeAction = oldResumeAction,
hasCheckedForResume = oldHasCheckedForResume,
- active = oldActive
+ active = oldActive,
)
onMediaDataLoaded(key, oldKey, mediaData)
}
@@ -1102,7 +1109,7 @@
private fun logSingleVsMultipleMediaAdded(
appUid: Int,
packageName: String,
- instanceId: InstanceId
+ instanceId: InstanceId,
) {
if (mediaDataRepository.mediaEntries.value.size == 1) {
logger.logSingleMediaPlayerInCarousel(appUid, packageName, instanceId)
@@ -1151,7 +1158,7 @@
private fun createActionsFromState(
packageName: String,
controller: MediaController,
- user: UserHandle
+ user: UserHandle,
): MediaButton? {
if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
return null
@@ -1189,7 +1196,7 @@
packageName,
ContentProvider.getUriWithoutUserId(uri),
Intent.FLAG_GRANT_READ_URI_PERMISSION,
- ContentProvider.getUserIdFromUri(uri, userId)
+ ContentProvider.getUserIdFromUri(uri, userId),
)
return loadBitmapFromUri(uri)
} catch (e: SecurityException) {
@@ -1226,7 +1233,7 @@
val scale =
MediaDataUtils.getScaleFactor(
APair(width, height),
- APair(artworkWidth, artworkHeight)
+ APair(artworkWidth, artworkHeight),
)
// Downscale if needed
@@ -1251,7 +1258,7 @@
.loadDrawable(context),
action,
context.getString(R.string.controls_media_resume),
- context.getDrawable(R.drawable.ic_media_play_container)
+ context.getDrawable(R.drawable.ic_media_play_container),
)
}
@@ -1291,7 +1298,7 @@
} else {
notifySmartspaceMediaDataRemoved(
smartspaceMediaData.targetId,
- immediately = false
+ immediately = false,
)
mediaDataRepository.setRecommendation(
SmartspaceMediaData(
@@ -1362,7 +1369,7 @@
private fun handlePossibleRemoval(
key: String,
removed: MediaData,
- notificationRemoved: Boolean = false
+ notificationRemoved: Boolean = false,
) {
val hasSession = removed.token != null
if (hasSession && removed.semanticActions != null) {
@@ -1387,7 +1394,7 @@
Log.d(
TAG,
"Notification ($notificationRemoved) and/or session " +
- "($hasSession) gone for inactive player $key"
+ "($hasSession) gone for inactive player $key",
)
}
convertToResumePlayer(key, removed)
@@ -1513,7 +1520,7 @@
data: MediaData,
immediately: Boolean = true,
receivedSmartspaceCardLatency: Int = 0,
- isSsReactivated: Boolean = false
+ isSsReactivated: Boolean = false,
) {}
/**
@@ -1526,7 +1533,7 @@
fun onSmartspaceMediaDataLoaded(
key: String,
data: SmartspaceMediaData,
- shouldPrioritize: Boolean = false
+ shouldPrioritize: Boolean = false,
) {}
/** Called whenever a previously existing Media notification was removed. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index a0fb0bf2..72650ea 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.controls.ui.controller
+import android.annotation.WorkerThread
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
@@ -41,6 +42,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Dumpable
+import com.android.systemui.Flags.mediaControlsUmoInflationInBackground
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -137,7 +139,7 @@
private val activityStarter: ActivityStarter,
private val systemClock: SystemClock,
@Main private val mainDispatcher: CoroutineDispatcher,
- @Main executor: DelayableExecutor,
+ @Main private val uiExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val mediaManager: MediaDataManager,
@@ -227,7 +229,7 @@
private var carouselLocale: Locale? = null
private val animationScaleObserver: ContentObserver =
- object : ContentObserver(executor, 0) {
+ object : ContentObserver(uiExecutor, 0) {
override fun onChange(selfChange: Boolean) {
if (!SceneContainerFlag.isEnabled) {
MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() }
@@ -350,7 +352,7 @@
MediaCarouselScrollHandler(
mediaCarousel,
pageIndicator,
- executor,
+ uiExecutor,
this::onSwipeToDismiss,
this::updatePageIndicatorLocation,
this::updateSeekbarListening,
@@ -458,7 +460,17 @@
isSsReactivated: Boolean,
) {
debugLogger.logMediaLoaded(key, data.active)
- if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) {
+ val onUiExecutionEnd =
+ if (mediaControlsUmoInflationInBackground()) {
+ Runnable {
+ if (immediately) {
+ updateHostVisibility()
+ }
+ }
+ } else {
+ null
+ }
+ if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated, onUiExecutionEnd)) {
// Log card received if a new resumable media card is added
MediaPlayerData.getMediaPlayer(key)?.let {
logSmartspaceCardReported(
@@ -980,6 +992,7 @@
oldKey: String?,
data: MediaData,
isSsReactivated: Boolean,
+ onUiExecutionEnd: Runnable? = null,
): Boolean =
traceSection("MediaCarouselController#addOrUpdatePlayer") {
MediaPlayerData.moveIfExists(oldKey, key)
@@ -987,76 +1000,119 @@
val curVisibleMediaKey =
MediaPlayerData.visiblePlayerKeys()
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
- if (existingPlayer == null) {
- val newPlayer = mediaControlPanelFactory.get()
- if (SceneContainerFlag.isEnabled) {
- newPlayer.mediaViewController.widthInSceneContainerPx = widthInSceneContainerPx
- newPlayer.mediaViewController.heightInSceneContainerPx =
- heightInSceneContainerPx
- }
- newPlayer.attachPlayer(
- MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
- )
- newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
- val lp =
- LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- )
- newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
- newPlayer.bindPlayer(data, key)
- newPlayer.setListening(
- mediaCarouselScrollHandler.visibleToUser && currentlyExpanded
- )
- MediaPlayerData.addMediaPlayer(
- key,
- data,
- newPlayer,
- systemClock,
- isSsReactivated,
- debugLogger,
- )
- updateViewControllerToState(newPlayer.mediaViewController, noAnimation = true)
- // Media data added from a recommendation card should starts playing.
- if (
- (shouldScrollToKey && data.isPlaying == true) ||
- (!shouldScrollToKey && data.active)
- ) {
- reorderAllPlayers(curVisibleMediaKey, key)
+ if (mediaControlsUmoInflationInBackground()) {
+ if (existingPlayer == null) {
+ bgExecutor.execute {
+ val mediaViewHolder = createMediaViewHolderInBg()
+ // Add the new player in the main thread.
+ uiExecutor.execute {
+ setupNewPlayer(
+ key,
+ data,
+ isSsReactivated,
+ curVisibleMediaKey,
+ mediaViewHolder,
+ )
+ updatePageIndicator()
+ mediaCarouselScrollHandler.onPlayersChanged()
+ mediaFrame.requiresRemeasuring = true
+ onUiExecutionEnd?.run()
+ }
+ }
} else {
- needsReordering = true
+ updatePlayer(key, data, isSsReactivated, curVisibleMediaKey, existingPlayer)
+ updatePageIndicator()
+ mediaCarouselScrollHandler.onPlayersChanged()
+ mediaFrame.requiresRemeasuring = true
+ onUiExecutionEnd?.run()
}
} else {
- existingPlayer.bindPlayer(data, key)
- MediaPlayerData.addMediaPlayer(
- key,
- data,
- existingPlayer,
- systemClock,
- isSsReactivated,
- debugLogger,
- )
- val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String()
- // In case of recommendations hits.
- // Check the playing status of media player and the package name.
- // To make sure we scroll to the right app's media player.
- if (
- isReorderingAllowed ||
- shouldScrollToKey &&
- data.isPlaying == true &&
- packageName == data.packageName
- ) {
- reorderAllPlayers(curVisibleMediaKey, key)
+ if (existingPlayer == null) {
+ val mediaViewHolder =
+ MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
+ setupNewPlayer(key, data, isSsReactivated, curVisibleMediaKey, mediaViewHolder)
} else {
- needsReordering = true
+ updatePlayer(key, data, isSsReactivated, curVisibleMediaKey, existingPlayer)
}
+ updatePageIndicator()
+ mediaCarouselScrollHandler.onPlayersChanged()
+ mediaFrame.requiresRemeasuring = true
+ onUiExecutionEnd?.run()
}
- updatePageIndicator()
- mediaCarouselScrollHandler.onPlayersChanged()
- mediaFrame.requiresRemeasuring = true
return existingPlayer == null
}
+ private fun updatePlayer(
+ key: String,
+ data: MediaData,
+ isSsReactivated: Boolean,
+ curVisibleMediaKey: MediaPlayerData.MediaSortKey?,
+ existingPlayer: MediaControlPanel,
+ ) {
+ existingPlayer.bindPlayer(data, key)
+ MediaPlayerData.addMediaPlayer(
+ key,
+ data,
+ existingPlayer,
+ systemClock,
+ isSsReactivated,
+ debugLogger,
+ )
+ val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String()
+ // In case of recommendations hits.
+ // Check the playing status of media player and the package name.
+ // To make sure we scroll to the right app's media player.
+ if (
+ isReorderingAllowed ||
+ shouldScrollToKey && data.isPlaying == true && packageName == data.packageName
+ ) {
+ reorderAllPlayers(curVisibleMediaKey, key)
+ } else {
+ needsReordering = true
+ }
+ }
+
+ private fun setupNewPlayer(
+ key: String,
+ data: MediaData,
+ isSsReactivated: Boolean,
+ curVisibleMediaKey: MediaPlayerData.MediaSortKey?,
+ mediaViewHolder: MediaViewHolder,
+ ) {
+ val newPlayer = mediaControlPanelFactory.get()
+ newPlayer.attachPlayer(mediaViewHolder)
+ newPlayer.mediaViewController.sizeChangedListener =
+ this@MediaCarouselController::updateCarouselDimensions
+ val lp =
+ LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ )
+ newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
+ newPlayer.bindPlayer(data, key)
+ newPlayer.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded)
+ MediaPlayerData.addMediaPlayer(
+ key,
+ data,
+ newPlayer,
+ systemClock,
+ isSsReactivated,
+ debugLogger,
+ )
+ updateViewControllerToState(newPlayer.mediaViewController, noAnimation = true)
+ // Media data added from a recommendation card should starts playing.
+ if ((shouldScrollToKey && data.isPlaying == true) || (!shouldScrollToKey && data.active)) {
+ reorderAllPlayers(curVisibleMediaKey, key)
+ } else {
+ needsReordering = true
+ }
+ }
+
+ @WorkerThread
+ private fun createMediaViewHolderInBg(): MediaViewHolder {
+ return MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
+ }
+
private fun addSmartspaceMediaRecommendations(
key: String,
data: SmartspaceMediaData,
@@ -1173,8 +1229,16 @@
val previousVisibleKey =
MediaPlayerData.visiblePlayerKeys()
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+ val onUiExecutionEnd = Runnable {
+ if (recreateMedia) {
+ reorderAllPlayers(previousVisibleKey)
+ }
+ }
- MediaPlayerData.mediaData().forEach { (key, data, isSsMediaRec) ->
+ val mediaDataList = MediaPlayerData.mediaData()
+ // Do not loop through the original list of media data because the re-addition of media data
+ // is being executed in background thread.
+ mediaDataList.forEach { (key, data, isSsMediaRec) ->
if (isSsMediaRec) {
val smartspaceMediaData = MediaPlayerData.smartspaceMediaData
removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
@@ -1185,6 +1249,7 @@
MediaPlayerData.shouldPrioritizeSs,
)
}
+ onUiExecutionEnd.run()
} else {
val isSsReactivated = MediaPlayerData.isSsReactivated(key)
if (recreateMedia) {
@@ -1195,11 +1260,9 @@
oldKey = null,
data = data,
isSsReactivated = isSsReactivated,
+ onUiExecutionEnd = onUiExecutionEnd,
)
}
- if (recreateMedia) {
- reorderAllPlayers(previousVisibleKey)
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt
index 8660d12..782da4b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.ui.controller
import com.android.app.tracing.traceSection
+import com.android.systemui.Flags.mediaControlsUmoInflationInBackground
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.util.animation.MeasurementOutput
@@ -71,23 +72,34 @@
*/
fun updateCarouselDimensions(
@MediaLocation location: Int,
- hostState: MediaHostState
+ hostState: MediaHostState,
): MeasurementOutput =
traceSection("MediaHostStatesManager#updateCarouselDimensions") {
val result = MeasurementOutput(0, 0)
+ var changed = false
for (controller in controllers) {
val measurement = controller.getMeasurementsForState(hostState)
measurement?.let {
if (it.measuredHeight > result.measuredHeight) {
result.measuredHeight = it.measuredHeight
+ changed = true
}
if (it.measuredWidth > result.measuredWidth) {
result.measuredWidth = it.measuredWidth
+ changed = true
}
}
}
- carouselSizes[location] = result
- return result
+ if (mediaControlsUmoInflationInBackground()) {
+ // Set carousel size if result measurements changed. This avoids setting carousel
+ // size when this method gets called before the addition of media view controllers
+ if (!carouselSizes.contains(location) || changed) {
+ carouselSizes[location] = result
+ }
+ } else {
+ carouselSizes[location] = result
+ }
+ return carouselSizes[location] ?: result
}
/** Add a callback to be called when a MediaState has updated */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
index 09a6181..5ddc347 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
@@ -20,6 +20,7 @@
import android.util.ArraySet
import android.view.View
import android.view.View.OnAttachStateChangeListener
+import com.android.systemui.Flags.mediaControlsUmoInflationInBackground
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
@@ -91,8 +92,10 @@
data: MediaData,
immediately: Boolean,
receivedSmartspaceCardLatency: Int,
- isSsReactivated: Boolean
+ isSsReactivated: Boolean,
) {
+ if (mediaControlsUmoInflationInBackground()) return
+
if (immediately) {
updateViewVisibility()
}
@@ -101,7 +104,7 @@
override fun onSmartspaceMediaDataLoaded(
key: String,
data: SmartspaceMediaData,
- shouldPrioritize: Boolean
+ shouldPrioritize: Boolean,
) {
updateViewVisibility()
}
@@ -171,7 +174,7 @@
input.widthMeasureSpec =
View.MeasureSpec.makeMeasureSpec(
View.MeasureSpec.getSize(input.widthMeasureSpec),
- View.MeasureSpec.EXACTLY
+ View.MeasureSpec.EXACTLY,
)
}
// This will trigger a state change that ensures that we now have a state
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index ba0d938..66ac01a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -180,6 +180,7 @@
qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
setListenerCollections()
+ lifecycleScope.launch { viewModel.activate() }
}
override fun onCreateView(
@@ -331,7 +332,7 @@
}
override fun setOverscrolling(overscrolling: Boolean) {
- viewModel.stackScrollerOverscrollingValue = overscrolling
+ viewModel.isStackScrollerOverscrolling = overscrolling
}
override fun setExpanded(qsExpanded: Boolean) {
@@ -410,11 +411,11 @@
qsTransitionFraction: Float,
qsSquishinessFraction: Float,
) {
- super.setTransitionToFullShadeProgress(
- isTransitioningToFullShade,
- qsTransitionFraction,
- qsSquishinessFraction,
- )
+ viewModel.isTransitioningToFullShade = isTransitioningToFullShade
+ viewModel.lockscreenToShadeProgressValue = qsTransitionFraction
+ if (isTransitioningToFullShade) {
+ viewModel.squishinessFractionValue = qsSquishinessFraction
+ }
}
override fun setFancyClipping(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 7300ee1..2d4e358 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -24,16 +24,19 @@
import com.android.systemui.Dumpable
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel.QSExpansionState
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printSection
@@ -50,6 +53,7 @@
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -61,13 +65,14 @@
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
private val sysuiStatusBarStateController: SysuiStatusBarStateController,
- private val keyguardBypassController: KeyguardBypassController,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
private val disableFlagsRepository: DisableFlagsRepository,
private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator,
private val configurationInteractor: ConfigurationInteractor,
private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
+ private val squishinessInteractor: TileSquishinessInteractor,
@Assisted private val lifecycleScope: LifecycleCoroutineScope,
-) : Dumpable {
+) : Dumpable, ExclusiveActivatable() {
val footerActionsViewModel =
footerActionsViewModelFactory.create(lifecycleScope).also {
lifecycleScope.launch { footerActionsController.init() }
@@ -110,7 +115,7 @@
_panelFraction.value = value
}
- private val _squishinessFraction = MutableStateFlow(0f)
+ private val _squishinessFraction = MutableStateFlow(1f)
var squishinessFractionValue: Float
get() = _squishinessFraction.value
set(value) {
@@ -131,7 +136,7 @@
private val _headerAnimating = MutableStateFlow(false)
private val _stackScrollerOverscrolling = MutableStateFlow(false)
- var stackScrollerOverscrollingValue: Boolean
+ var isStackScrollerOverscrolling: Boolean
get() = _stackScrollerOverscrolling.value
set(value) {
_stackScrollerOverscrolling.value = value
@@ -150,8 +155,6 @@
disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled(),
)
- private val _showCollapsedOnKeyguard = MutableStateFlow(false)
-
private val _keyguardAndExpanded = MutableStateFlow(false)
/**
@@ -177,21 +180,65 @@
awaitClose { sysuiStatusBarStateController.removeCallback(callback) }
}
+ .onStart { emit(sysuiStatusBarStateController.state) }
.stateIn(
lifecycleScope,
SharingStarted.WhileSubscribed(),
sysuiStatusBarStateController.state,
)
+ private val isKeyguardState =
+ statusBarState
+ .map { it == StatusBarState.KEYGUARD }
+ .stateIn(
+ lifecycleScope,
+ SharingStarted.WhileSubscribed(),
+ statusBarState.value == StatusBarState.KEYGUARD,
+ )
+
private val _viewHeight = MutableStateFlow(0)
private val _headerTranslation = MutableStateFlow(0f)
private val _inSplitShade = MutableStateFlow(false)
+ var isInSplitShade: Boolean
+ get() = _inSplitShade.value
+ set(value) {
+ _inSplitShade.value = value
+ }
private val _transitioningToFullShade = MutableStateFlow(false)
+ var isTransitioningToFullShade: Boolean
+ get() = _transitioningToFullShade.value
+ set(value) {
+ _transitioningToFullShade.value = value
+ }
- private val _lockscreenToShadeProgress = MutableStateFlow(false)
+ private val isBypassEnabled = deviceEntryInteractor.isBypassEnabled
+
+ private val showCollapsedOnKeyguard =
+ combine(
+ isBypassEnabled,
+ _transitioningToFullShade,
+ _inSplitShade,
+ ::calculateShowCollapsedOnKeyguard,
+ )
+ .stateIn(
+ lifecycleScope,
+ SharingStarted.WhileSubscribed(),
+ calculateShowCollapsedOnKeyguard(
+ isBypassEnabled.value,
+ isTransitioningToFullShade,
+ isInSplitShade,
+ ),
+ )
+
+ private val _lockscreenToShadeProgress = MutableStateFlow(0.0f)
+ var lockscreenToShadeProgressValue: Float
+ get() = _lockscreenToShadeProgress.value
+ set(value) {
+ _lockscreenToShadeProgress.value = value
+ }
private val _overscrolling = MutableStateFlow(false)
@@ -212,12 +259,32 @@
_heightOverride.value = value
}
+ private val forceQS =
+ combine(
+ _qsExpanded,
+ _stackScrollerOverscrolling,
+ isKeyguardState,
+ showCollapsedOnKeyguard,
+ ::calculateForceQs,
+ )
+ .stateIn(
+ lifecycleScope,
+ SharingStarted.WhileSubscribed(),
+ calculateForceQs(
+ isQSExpanded,
+ isStackScrollerOverscrolling,
+ isKeyguardState.value,
+ showCollapsedOnKeyguard.value,
+ ),
+ )
+
val expansionState: StateFlow<QSExpansionState> =
- combine(_stackScrollerOverscrolling, _qsExpanded, _qsExpansion) { args: Array<Any> ->
- val expansion = args[2] as Float
- QSExpansionState(expansion.coerceIn(0f, 1f))
- }
- .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState(0f))
+ combine(_qsExpansion, forceQS, ::calculateExpansionState)
+ .stateIn(
+ lifecycleScope,
+ SharingStarted.WhileSubscribed(),
+ calculateExpansionState(_qsExpansion.value, forceQS.value),
+ )
/**
* Accessibility action for collapsing/expanding QS. The provided runnable is responsible for
@@ -225,6 +292,16 @@
*/
var collapseExpandAccessibilityAction: Runnable? = null
+ override suspend fun onActivated(): Nothing {
+ hydrateSquishinessInteractor()
+ }
+
+ private suspend fun hydrateSquishinessInteractor(): Nothing {
+ _squishinessFraction.collect {
+ squishinessInteractor.setSquishinessValue(it.constrainSquishiness())
+ }
+ }
+
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.asIndenting().run {
printSection("Quick Settings state") {
@@ -238,13 +315,17 @@
println("panelExpansionFraction", panelExpansionFractionValue)
println("squishinessFraction", squishinessFractionValue)
println("expansionState", expansionState.value)
+ println("forceQS", forceQS.value)
}
printSection("Shade state") {
- println("stackOverscrolling", stackScrollerOverscrollingValue)
+ println("stackOverscrolling", isStackScrollerOverscrolling)
println("statusBarState", StatusBarState.toString(statusBarState.value))
+ println("isKeyguardState", isKeyguardState.value)
println("isSmallScreen", isSmallScreenValue)
println("heightOverride", "${heightOverrideValue}px")
println("qqsHeaderHeight", "${qqsHeaderHeight.value}px")
+ println("isSplitShade", isInSplitShade)
+ println("showCollapsedOnKeyguard", showCollapsedOnKeyguard.value)
}
}
}
@@ -257,3 +338,35 @@
// In the future, this will have other relevant elements like squishiness.
data class QSExpansionState(@FloatRange(0.0, 1.0) val progress: Float)
}
+
+private fun Float.constrainSquishiness(): Float {
+ return (0.1f + this * 0.9f).coerceIn(0f, 1f)
+}
+
+// Helper methods for combining flows.
+
+private fun calculateExpansionState(expansion: Float, forceQs: Boolean): QSExpansionState {
+ return if (forceQs) {
+ QSExpansionState(1f)
+ } else {
+ QSExpansionState(expansion.coerceIn(0f, 1f))
+ }
+}
+
+private fun calculateForceQs(
+ isQSExpanded: Boolean,
+ isStackOverScrolling: Boolean,
+ isKeyguardShowing: Boolean,
+ shouldShowCollapsedOnKeyguard: Boolean,
+): Boolean {
+ return (isQSExpanded || isStackOverScrolling) &&
+ (isKeyguardShowing && !shouldShowCollapsedOnKeyguard)
+}
+
+private fun calculateShowCollapsedOnKeyguard(
+ isBypassEnabled: Boolean,
+ isTransitioningToFullShade: Boolean,
+ isInSplitShade: Boolean,
+): Boolean {
+ return isBypassEnabled || (isTransitioningToFullShade && !isInSplitShade)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepository.kt
new file mode 100644
index 0000000..76ba9af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class TileSquishinessRepository @Inject constructor() {
+ private val _squishiness = MutableStateFlow(1f)
+ val squishiness = _squishiness.asStateFlow()
+
+ fun setSquishinessValue(value: Float) {
+ _squishiness.value = value
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractor.kt
new file mode 100644
index 0000000..4fdbc76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.data.repository.TileSquishinessRepository
+import javax.inject.Inject
+
+@SysUISingleton
+class TileSquishinessInteractor
+@Inject
+constructor(private val repository: TileSquishinessRepository) {
+ val squishiness = repository.squishiness
+
+ fun setSquishinessValue(value: Float) {
+ repository.setSquishinessValue(value)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index 8998a7f..a645b51 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -41,6 +41,7 @@
val sizedTiles by
viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList())
val tiles = sizedTiles.fastMap { it.tile }
+ val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle()
DisposableEffect(tiles) {
val token = Any()
@@ -62,6 +63,7 @@
tile = it.tile,
iconOnly = it.isIcon,
modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)),
+ squishiness = { squishiness },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 8c2fb25..bf4c113 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -73,6 +73,7 @@
secondaryLabel: String?,
icon: Icon,
colors: TileColors,
+ squishiness: () -> Float,
accessibilityUiState: AccessibilityUiState? = null,
toggleClickSupported: Boolean = false,
iconShape: Shape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius),
@@ -89,6 +90,7 @@
modifier =
Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) {
Modifier.clip(iconShape)
+ .verticalSquish(squishiness)
.background(colors.iconBackground, { 1f })
.combinedClickable(
onClick = onClick,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index e6edba5..3ba49ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -33,6 +33,7 @@
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.TileSquishinessViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey
@@ -45,6 +46,7 @@
constructor(
private val iconTilesViewModel: IconTilesViewModel,
private val gridSizeViewModel: FixedColumnsSizeViewModel,
+ private val squishinessViewModel: TileSquishinessViewModel,
) : PaginatableGridLayout {
@Composable
@@ -60,6 +62,7 @@
}
val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) }
+ val squishiness by squishinessViewModel.squishiness.collectAsStateWithLifecycle()
VerticalSpannedGrid(
columns = columns,
@@ -72,6 +75,7 @@
tile = it.tile,
iconOnly = iconTilesViewModel.isIconTile(it.tile.spec),
modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)),
+ squishiness = { squishiness },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
new file mode 100644
index 0000000..ada1ef4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.compose.infinitegrid
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import kotlin.math.roundToInt
+
+/**
+ * Modifier to squish the vertical bounds of a composable (usually a QS tile).
+ *
+ * It will squish the vertical bounds of the inner composable node by the value returned by
+ * [squishiness] on the measure/layout pass.
+ *
+ * The squished composable will be center aligned.
+ */
+fun Modifier.verticalSquish(squishiness: () -> Float): Modifier {
+ return layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ val actualHeight = placeable.height
+ val squishedHeight = actualHeight * squishiness()
+ // Center the content by moving it UP (squishedHeight < actualHeight)
+ val scroll = (squishedHeight - actualHeight) / 2
+
+ layout(placeable.width, squishedHeight.roundToInt()) {
+ placeable.place(0, scroll.roundToInt())
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index afcbed6d..4bd5b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -98,7 +98,12 @@
}
@Composable
-fun Tile(tile: TileViewModel, iconOnly: Boolean, modifier: Modifier) {
+fun Tile(
+ tile: TileViewModel,
+ iconOnly: Boolean,
+ squishiness: () -> Float,
+ modifier: Modifier = Modifier,
+) {
val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
val resources = resources()
val uiState = remember(state, resources) { state.toUiState(resources) }
@@ -119,6 +124,7 @@
onClick = tile::onClick,
onLongClick = tile::onLongClick,
uiState = uiState,
+ squishiness = squishiness,
modifier = modifier,
) { expandable ->
val icon = getTileIcon(icon = uiState.icon)
@@ -144,6 +150,7 @@
},
onLongClick = { tile.onLongClick(expandable) },
accessibilityUiState = uiState.accessibilityUiState,
+ squishiness = squishiness,
)
}
}
@@ -155,12 +162,17 @@
shape: Shape,
iconOnly: Boolean,
uiState: TileUiState,
+ squishiness: () -> Float,
modifier: Modifier = Modifier,
onClick: (Expandable) -> Unit = {},
onLongClick: (Expandable) -> Unit = {},
content: @Composable BoxScope.(Expandable) -> Unit,
) {
- Expandable(color = color, shape = shape, modifier = modifier.clip(shape)) {
+ Expandable(
+ color = color,
+ shape = shape,
+ modifier = modifier.clip(shape).verticalSquish(squishiness),
+ ) {
val longPressLabel = longPressLabel()
Box(
modifier =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index eee905f..88e3019 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -42,6 +42,7 @@
tilesInteractor: CurrentTilesInteractor,
fixedColumnsSizeViewModel: FixedColumnsSizeViewModel,
quickQuickSettingsRowInteractor: QuickQuickSettingsRowInteractor,
+ val squishinessViewModel: TileSquishinessViewModel,
private val iconTilesViewModel: IconTilesViewModel,
@Application private val applicationScope: CoroutineScope,
) {
@@ -52,7 +53,7 @@
quickQuickSettingsRowInteractor.rows.stateIn(
applicationScope,
SharingStarted.WhileSubscribed(),
- quickQuickSettingsRowInteractor.defaultRows
+ quickQuickSettingsRowInteractor.defaultRows,
)
val tileViewModels: StateFlow<List<SizedTile<TileViewModel>>> =
@@ -60,12 +61,7 @@
.flatMapLatest { columns ->
tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
tiles
- .map {
- SizedTileImpl(
- TileViewModel(it.tile, it.spec),
- it.spec.width,
- )
- }
+ .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width) }
.let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
}
}
@@ -73,15 +69,10 @@
applicationScope,
SharingStarted.WhileSubscribed(),
tilesInteractor.currentTiles.value
- .map {
- SizedTileImpl(
- TileViewModel(it.tile, it.spec),
- it.spec.width,
- )
- }
+ .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width) }
.let {
splitInRowsSequence(it, columns.value).take(rows.value).toList().flatten()
- }
+ },
)
private val TileSpec.width: Int
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModel.kt
new file mode 100644
index 0000000..0c4d5de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
+import javax.inject.Inject
+
+/** View model to track the squishiness of tiles. */
+class TileSquishinessViewModel
+@Inject
+constructor(tileSquishinessInteractor: TileSquishinessInteractor) {
+ val squishiness = tileSquishinessInteractor.squishiness
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
new file mode 100644
index 0000000..57c8bc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.core
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.os.Binder
+import android.os.RemoteException
+import android.view.WindowInsets
+import com.android.internal.statusbar.IStatusBarService
+import com.android.internal.statusbar.RegisterStatusBarResult
+import com.android.systemui.CoreStartable
+import com.android.systemui.InitController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.navigationbar.NavigationBarController
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import dagger.Lazy
+import javax.inject.Inject
+
+@SysUISingleton
+class CommandQueueInitializer
+@Inject
+constructor(
+ private val context: Context,
+ private val commandQueue: CommandQueue,
+ private val commandQueueCallbacksLazy: Lazy<CommandQueue.Callbacks>,
+ private val statusBarModeRepository: StatusBarModeRepositoryStore,
+ private val initController: InitController,
+ private val barService: IStatusBarService,
+ private val navigationBarController: NavigationBarController,
+) : CoreStartable {
+
+ override fun start() {
+ StatusBarSimpleFragment.assertInNewMode()
+ val result: RegisterStatusBarResult =
+ try {
+ barService.registerStatusBar(commandQueue)
+ } catch (ex: RemoteException) {
+ ex.rethrowFromSystemServer()
+ return
+ }
+
+ createNavigationBar(result)
+
+ if ((result.mTransientBarTypes and WindowInsets.Type.statusBars()) != 0) {
+ statusBarModeRepository.defaultDisplay.showTransient()
+ }
+ val displayId = context.display.displayId
+ val commandQueueCallbacks = commandQueueCallbacksLazy.get()
+ commandQueueCallbacks.onSystemBarAttributesChanged(
+ displayId,
+ result.mAppearance,
+ result.mAppearanceRegions,
+ result.mNavbarColorManagedByIme,
+ result.mBehavior,
+ result.mRequestedVisibleTypes,
+ result.mPackageName,
+ result.mLetterboxDetails,
+ )
+
+ // StatusBarManagerService has a back up of IME token and it's restored here.
+ commandQueueCallbacks.setImeWindowStatus(
+ displayId,
+ result.mImeWindowVis,
+ result.mImeBackDisposition,
+ result.mShowImeSwitcher,
+ )
+
+ // Set up the initial icon state
+ val numIcons: Int = result.mIcons.size
+ for (i in 0 until numIcons) {
+ commandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i))
+ }
+
+ // set the initial view visibility
+ val disabledFlags1 = result.mDisabledFlags1
+ val disabledFlags2 = result.mDisabledFlags2
+ initController.addPostInitTask {
+ commandQueue.disable(displayId, disabledFlags1, disabledFlags2, /* animate= */ false)
+ try {
+ // NOTE(b/262059863): Force-update the disable flags after applying the flags
+ // returned from registerStatusBar(). The result's disabled flags may be stale
+ // if StatusBarManager's disabled flags are updated between registering the bar
+ // and this handling this post-init task. We force an update in this case, and use a
+ // new token to not conflict with any other disabled flags already requested by
+ // SysUI
+ val token = Binder()
+ barService.disable(StatusBarManager.DISABLE_HOME, token, context.packageName)
+ barService.disable(0, token, context.packageName)
+ } catch (ex: RemoteException) {
+ ex.rethrowFromSystemServer()
+ }
+ }
+ }
+
+ private fun createNavigationBar(result: RegisterStatusBarResult) {
+ navigationBarController.createNavigationBars(/* includeDefaultDisplay= */ true, result)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
new file mode 100644
index 0000000..8bd990b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.core
+
+import android.view.View
+import com.android.systemui.CoreStartable
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.plugins.PluginDependencyProvider
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.shade.NotificationShadeWindowViewController
+import com.android.systemui.shade.ShadeSurface
+import com.android.systemui.statusbar.AutoHideUiElement
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.phone.AutoHideController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.data.model.StatusBarWindowState
+import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
+import com.android.wm.shell.bubbles.Bubbles
+import dagger.Lazy
+import java.io.PrintWriter
+import java.util.Optional
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+/**
+ * Class responsible for managing the lifecycle and state of the status bar.
+ *
+ * It is a temporary class, created to pull status bar related logic out of CentralSurfacesImpl. The
+ * plan is break it out into individual classes.
+ */
+@SysUISingleton
+class StatusBarOrchestrator
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val statusBarInitializer: StatusBarInitializer,
+ private val statusBarWindowController: StatusBarWindowController,
+ private val statusBarModeRepository: StatusBarModeRepositoryStore,
+ private val demoModeController: DemoModeController,
+ private val pluginDependencyProvider: PluginDependencyProvider,
+ private val autoHideController: AutoHideController,
+ private val remoteInputManager: NotificationRemoteInputManager,
+ private val notificationShadeWindowViewControllerLazy:
+ Lazy<NotificationShadeWindowViewController>,
+ private val shadeSurface: ShadeSurface,
+ private val bubblesOptional: Optional<Bubbles>,
+ private val statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore,
+ powerInteractor: PowerInteractor,
+ primaryBouncerInteractor: PrimaryBouncerInteractor,
+) : CoreStartable {
+
+ private val phoneStatusBarViewController =
+ MutableStateFlow<PhoneStatusBarViewController?>(value = null)
+
+ private val phoneStatusBarTransitions =
+ MutableStateFlow<PhoneStatusBarTransitions?>(value = null)
+
+ private val shouldAnimateNextBarModeChange =
+ combine(
+ statusBarModeRepository.defaultDisplay.isTransientShown,
+ powerInteractor.isAwake,
+ statusBarWindowStateRepositoryStore.defaultDisplay.windowState,
+ ) { isTransientShown, isDeviceAwake, statusBarWindowState ->
+ !isTransientShown &&
+ isDeviceAwake &&
+ statusBarWindowState != StatusBarWindowState.Hidden
+ }
+
+ private val controllerAndBouncerShowing =
+ combine(
+ phoneStatusBarViewController.filterNotNull(),
+ primaryBouncerInteractor.isShowing,
+ ::Pair,
+ )
+
+ private val barTransitionsAndDeviceAsleep =
+ combine(phoneStatusBarTransitions.filterNotNull(), powerInteractor.isAsleep, ::Pair)
+
+ private val statusBarVisible =
+ combine(
+ statusBarModeRepository.defaultDisplay.statusBarMode,
+ statusBarWindowStateRepositoryStore.defaultDisplay.windowState,
+ ) { mode, statusBarWindowState ->
+ mode != StatusBarMode.LIGHTS_OUT &&
+ mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT &&
+ statusBarWindowState != StatusBarWindowState.Hidden
+ }
+
+ private val barModeUpdate =
+ combine(
+ shouldAnimateNextBarModeChange,
+ phoneStatusBarTransitions.filterNotNull(),
+ statusBarModeRepository.defaultDisplay.statusBarMode,
+ ::Triple,
+ )
+ .distinctUntilChangedBy { (_, barTransitions, statusBarMode) ->
+ // We only want to collect when either bar transitions or status bar mode
+ // changed.
+ Pair(barTransitions, statusBarMode)
+ }
+
+ override fun start() {
+ StatusBarSimpleFragment.assertInNewMode()
+ applicationScope.launch {
+ launch {
+ controllerAndBouncerShowing.collect { (controller, bouncerShowing) ->
+ setBouncerShowingForStatusBarComponents(controller, bouncerShowing)
+ }
+ }
+ launch {
+ barTransitionsAndDeviceAsleep.collect { (barTransitions, deviceAsleep) ->
+ if (deviceAsleep) {
+ barTransitions.finishAnimations()
+ }
+ }
+ }
+ launch { statusBarVisible.collect { updateBubblesVisibility(it) } }
+ launch {
+ barModeUpdate.collect { (animate, barTransitions, statusBarMode) ->
+ updateBarMode(animate, barTransitions, statusBarMode)
+ }
+ }
+ }
+ createAndAddWindow()
+ setupPluginDependencies()
+ setUpAutoHide()
+ }
+
+ private fun createAndAddWindow() {
+ initializeStatusBarFragment()
+ statusBarWindowController.attach()
+ }
+
+ private fun initializeStatusBarFragment() {
+ statusBarInitializer.statusBarViewUpdatedListener =
+ object : StatusBarInitializer.OnStatusBarViewUpdatedListener {
+ override fun onStatusBarViewUpdated(
+ statusBarViewController: PhoneStatusBarViewController,
+ statusBarTransitions: PhoneStatusBarTransitions,
+ ) {
+ phoneStatusBarViewController.value = statusBarViewController
+ phoneStatusBarTransitions.value = statusBarTransitions
+
+ notificationShadeWindowViewControllerLazy
+ .get()
+ .setStatusBarViewController(statusBarViewController)
+ // Ensure we re-propagate panel expansion values to the panel controller and
+ // any listeners it may have, such as PanelBar. This will also ensure we
+ // re-display the notification panel if necessary (for example, if
+ // a heads-up notification was being displayed and should continue being
+ // displayed).
+ shadeSurface.updateExpansionAndVisibility()
+ }
+ }
+ }
+
+ private fun setupPluginDependencies() {
+ pluginDependencyProvider.allowPluginDependency(DarkIconDispatcher::class.java)
+ pluginDependencyProvider.allowPluginDependency(StatusBarStateController::class.java)
+ }
+
+ private fun setUpAutoHide() {
+ autoHideController.setStatusBar(
+ object : AutoHideUiElement {
+ override fun synchronizeState() {}
+
+ override fun shouldHideOnTouch(): Boolean {
+ return !remoteInputManager.isRemoteInputActive
+ }
+
+ override fun isVisible(): Boolean {
+ return statusBarModeRepository.defaultDisplay.isTransientShown.value
+ }
+
+ override fun hide() {
+ statusBarModeRepository.defaultDisplay.clearTransient()
+ }
+ })
+ }
+
+ private fun updateBarMode(
+ animate: Boolean,
+ barTransitions: PhoneStatusBarTransitions,
+ barMode: StatusBarMode,
+ ) {
+ if (!demoModeController.isInDemoMode) {
+ barTransitions.transitionTo(barMode.toTransitionModeInt(), animate)
+ }
+ autoHideController.touchAutoHide()
+ }
+
+ private fun updateBubblesVisibility(statusBarVisible: Boolean) {
+ bubblesOptional.ifPresent { bubbles: Bubbles ->
+ bubbles.onStatusBarVisibilityChanged(statusBarVisible)
+ }
+ }
+
+ private fun setBouncerShowingForStatusBarComponents(
+ controller: PhoneStatusBarViewController,
+ bouncerShowing: Boolean,
+ ) {
+ val importance =
+ if (bouncerShowing) {
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+ controller.setImportantForAccessibility(importance)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println(statusBarWindowStateRepositoryStore.defaultDisplay.windowState.value)
+ CentralSurfaces.dumpBarTransitions(
+ pw,
+ "PhoneStatusBarTransitions",
+ phoneStatusBarTransitions.value,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 3903ff3..cf238d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -46,6 +46,7 @@
*/
@Module(includes = [StatusBarDataLayerModule::class, SystemBarUtilsProxyImpl.Module::class])
abstract class StatusBarModule {
+
@Binds
@IntoMap
@ClassKey(OngoingCallController::class)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
index da29b0f..ec5ebc36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
@@ -43,7 +43,7 @@
entry: NotificationEntry,
builder: Notification.Builder,
systemUIContext: Context,
- packageContext: Context
+ packageContext: Context,
): RichOngoingContentModel?
}
@@ -52,7 +52,7 @@
entry: NotificationEntry,
builder: Notification.Builder,
systemUIContext: Context,
- packageContext: Context
+ packageContext: Context,
): RichOngoingContentModel? = null
}
@@ -68,7 +68,7 @@
entry: NotificationEntry,
builder: Notification.Builder,
systemUIContext: Context,
- packageContext: Context
+ packageContext: Context,
): RichOngoingContentModel? {
val sbn = entry.sbn
val notification = sbn.notification
@@ -89,7 +89,7 @@
null
}
}
- } else if (builder.style is Notification.EnRouteStyle) {
+ } else if (builder.style is Notification.ProgressStyle) {
parseEnRouteNotification(notification, icon)
} else null
} catch (e: Exception) {
@@ -104,7 +104,7 @@
*/
private fun parseTimerNotification(
notification: Notification,
- icon: IconModel
+ icon: IconModel,
): TimerContentModel {
// sortKey=1 0|↺7|RUNNING|▶16:21:58.523|Σ0:05:00|Δ0:00:03|⏳0:04:57
// sortKey=1 0|↺7|PAUSED|Σ0:05:00|Δ0:04:54|⏳0:00:06
@@ -132,7 +132,7 @@
resumeIntent = notification.findStartIntent(),
addMinuteAction = notification.findAddMinuteAction(),
resetAction = notification.findResetAction(),
- )
+ ),
)
}
"RUNNING" -> {
@@ -149,7 +149,7 @@
pauseIntent = notification.findPauseIntent(),
addMinuteAction = notification.findAddMinuteAction(),
resetAction = notification.findResetAction(),
- )
+ ),
)
}
else -> error("unknown state ($state) in sortKey=$sortKey")
@@ -192,7 +192,7 @@
val localDateTime =
LocalDateTime.of(
LocalDate.now(),
- LocalTime.of(hour.toInt(), minute.toInt(), second.toInt(), millis.toInt() * 1000000)
+ LocalTime.of(hour.toInt(), minute.toInt(), second.toInt(), millis.toInt() * 1000000),
)
val offset = ZoneId.systemDefault().rules.getOffset(localDateTime)
return localDateTime.toInstant(offset).toEpochMilli()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 57be629..0ad22e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -61,6 +61,7 @@
import com.android.systemui.keyguard.ui.viewmodel.OccludedToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OffToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
@@ -132,6 +133,7 @@
private val occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
private val occludedToGoneTransitionViewModel: OccludedToGoneTransitionViewModel,
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ private val offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel,
private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
private val primaryBouncerToLockscreenTransitionViewModel:
PrimaryBouncerToLockscreenTransitionViewModel,
@@ -444,6 +446,7 @@
occludedToAodTransitionViewModel.lockscreenAlpha,
occludedToGoneTransitionViewModel.notificationAlpha(viewState),
occludedToLockscreenTransitionViewModel.lockscreenAlpha,
+ offToLockscreenTransitionViewModel.lockscreenAlpha,
primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
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 1d3f0e1..5f4f72f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -241,10 +241,10 @@
import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
import com.android.wm.shell.startingsurface.StartingSurface;
-import dagger.Lazy;
-
import dalvik.annotation.optimization.NeverCompile;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;
@@ -304,6 +304,7 @@
};
void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
+ StatusBarSimpleFragment.assertInLegacyMode();
mStatusBarWindowState = state;
updateBubblesVisibility();
}
@@ -813,8 +814,9 @@
mStartingSurfaceOptional = startingSurfaceOptional;
mDreamManager = dreamManager;
lockscreenShadeTransitionController.setCentralSurfaces(this);
- statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
-
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
+ }
mScreenOffAnimationController = screenOffAnimationController;
ShadeExpansionListener shadeExpansionListener = this::onPanelExpansionChanged;
@@ -901,10 +903,12 @@
mWallpaperSupported = mWallpaperManager.isWallpaperSupported();
RegisterStatusBarResult result = null;
- try {
- result = mBarService.registerStatusBar(mCommandQueue);
- } catch (RemoteException ex) {
- ex.rethrowFromSystemServer();
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ try {
+ result = mBarService.registerStatusBar(mCommandQueue);
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
}
createAndAddWindows(result);
@@ -912,30 +916,45 @@
// Set up the initial notification state. This needs to happen before CommandQueue.disable()
setUpPresenter();
- if ((result.mTransientBarTypes & WindowInsets.Type.statusBars()) != 0) {
- mStatusBarModeRepository.getDefaultDisplay().showTransient();
- }
- mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance,
- result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior,
- result.mRequestedVisibleTypes, result.mPackageName, result.mLetterboxDetails);
-
- // StatusBarManagerService has a back up of IME token and it's restored here.
- mCommandQueueCallbacks.setImeWindowStatus(mDisplayId, result.mImeWindowVis,
- result.mImeBackDisposition, result.mShowImeSwitcher);
-
- // Set up the initial icon state
- int numIcons = result.mIcons.size();
- for (int i = 0; i < numIcons; i++) {
- mCommandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i));
- }
-
- if (DEBUG) {
- Log.d(TAG, String.format(
- "init: icons=%d disabled=0x%08x lights=0x%08x imeButton=0x%08x",
- numIcons,
- result.mDisabledFlags1,
+ // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+ // StatusBarOrchestrator
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ if ((result.mTransientBarTypes & WindowInsets.Type.statusBars()) != 0) {
+ mStatusBarModeRepository.getDefaultDisplay().showTransient();
+ }
+ mCommandQueueCallbacks.onSystemBarAttributesChanged(
+ mDisplayId,
result.mAppearance,
- result.mImeWindowVis));
+ result.mAppearanceRegions,
+ result.mNavbarColorManagedByIme,
+ result.mBehavior,
+ result.mRequestedVisibleTypes,
+ result.mPackageName,
+ result.mLetterboxDetails);
+
+ // StatusBarManagerService has a back up of IME token and it's restored here.
+ mCommandQueueCallbacks.setImeWindowStatus(
+ mDisplayId,
+ result.mImeWindowVis,
+ result.mImeBackDisposition,
+ result.mShowImeSwitcher);
+
+ // Set up the initial icon state
+ int numIcons = result.mIcons.size();
+ for (int i = 0; i < numIcons; i++) {
+ mCommandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i));
+ }
+
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ String.format(
+ "init: icons=%d disabled=0x%08x lights=0x%08x imeButton=0x%08x",
+ numIcons,
+ result.mDisabledFlags1,
+ result.mAppearance,
+ result.mImeWindowVis));
+ }
}
IntentFilter internalFilter = new IntentFilter();
@@ -1005,24 +1024,30 @@
mAccessibilityFloatingMenuController.init();
- // set the initial view visibility
- int disabledFlags1 = result.mDisabledFlags1;
- int disabledFlags2 = result.mDisabledFlags2;
- mInitController.addPostInitTask(() -> {
- setUpDisableFlags(disabledFlags1, disabledFlags2);
- try {
- // NOTE(b/262059863): Force-update the disable flags after applying the flags
- // returned from registerStatusBar(). The result's disabled flags may be stale
- // if StatusBarManager's disabled flags are updated between registering the bar and
- // this handling this post-init task. We force an update in this case, and use a new
- // token to not conflict with any other disabled flags already requested by SysUI
- Binder token = new Binder();
- mBarService.disable(DISABLE_HOME, token, mContext.getPackageName());
- mBarService.disable(0, token, mContext.getPackageName());
- } catch (RemoteException ex) {
- ex.rethrowFromSystemServer();
- }
- });
+ // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+ // StatusBarOrchestrator
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ // set the initial view visibility
+ int disabledFlags1 = result.mDisabledFlags1;
+ int disabledFlags2 = result.mDisabledFlags2;
+ mInitController.addPostInitTask(
+ () -> {
+ setUpDisableFlags(disabledFlags1, disabledFlags2);
+ try {
+ // NOTE(b/262059863): Force-update the disable flags after applying the
+ // flags returned from registerStatusBar(). The result's disabled flags
+ // may be stale if StatusBarManager's disabled flags are updated between
+ // registering the bar and this handling this post-init task. We force
+ // an update in this case, and use a new token to not conflict with any
+ // other disabled flags already requested by SysUI
+ Binder token = new Binder();
+ mBarService.disable(DISABLE_HOME, token, mContext.getPackageName());
+ mBarService.disable(0, token, mContext.getPackageName());
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ });
+ }
registerCallbacks();
@@ -1101,7 +1126,7 @@
/**
* @deprecated use {@link
- * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead.
+ * WindowRootViewVisibilityInteractor#isLockscreenOrShadeVisible()} instead.
*/ @VisibleForTesting
@Deprecated
void initShadeVisibilityListener() {
@@ -1168,13 +1193,16 @@
mWallpaperController.setRootView(getNotificationShadeWindowView());
mDemoModeController.addCallback(mDemoModeCallback);
- mJavaAdapter.alwaysCollectFlow(
- mStatusBarModeRepository.getDefaultDisplay().isTransientShown(),
- this::onTransientShownChanged);
- mJavaAdapter.alwaysCollectFlow(
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode(),
- this::updateBarMode);
-
+ // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+ // StatusBarOrchestrator.
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ mJavaAdapter.alwaysCollectFlow(
+ mStatusBarModeRepository.getDefaultDisplay().isTransientShown(),
+ this::onTransientShownChanged);
+ mJavaAdapter.alwaysCollectFlow(
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode(),
+ this::updateBarMode);
+ }
mCommandQueueCallbacks = mCommandQueueCallbacksLazy.get();
mCommandQueue.addCallback(mCommandQueueCallbacks);
@@ -1184,59 +1212,70 @@
mShadeExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
mWakeUpCoordinator.onPanelExpansionChanged(currentState);
- // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
- mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
- mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class);
-
- // Set up CollapsedStatusBarFragment and PhoneStatusBarView
- mStatusBarInitializer.setStatusBarViewUpdatedListener(
- (statusBarViewController, statusBarTransitions) -> {
- mPhoneStatusBarViewController = statusBarViewController;
- mStatusBarTransitions = statusBarTransitions;
- getNotificationShadeWindowViewController()
- .setStatusBarViewController(mPhoneStatusBarViewController);
- // Ensure we re-propagate panel expansion values to the panel controller and
- // any listeners it may have, such as PanelBar. This will also ensure we
- // re-display the notification panel if necessary (for example, if
- // a heads-up notification was being displayed and should continue being
- // displayed).
- mShadeSurface.updateExpansionAndVisibility();
- setBouncerShowingForStatusBarComponents(mBouncerShowing);
- checkBarModes();
- });
- // When the flag is on, we register the fragment as a core startable and this is not needed
+ // When the StatusBarSimpleFragment flag is enabled, all this logic will be done in
+ // StatusBarOrchestrator.
if (!StatusBarSimpleFragment.isEnabled()) {
+ // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
+ mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
+ mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class);
+
+ // Set up CollapsedStatusBarFragment and PhoneStatusBarView
+ mStatusBarInitializer.setStatusBarViewUpdatedListener(
+ (statusBarViewController, statusBarTransitions) -> {
+
+ mPhoneStatusBarViewController = statusBarViewController;
+ mStatusBarTransitions = statusBarTransitions;
+ getNotificationShadeWindowViewController()
+ .setStatusBarViewController(mPhoneStatusBarViewController);
+ // Ensure we re-propagate panel expansion values to the panel controller and
+ // any listeners it may have, such as PanelBar. This will also ensure we
+ // re-display the notification panel if necessary (for example, if
+ // a heads-up notification was being displayed and should continue being
+ // displayed).
+ mShadeSurface.updateExpansionAndVisibility();
+ setBouncerShowingForStatusBarComponents(mBouncerShowing);
+ checkBarModes();
+ });
+ // When the flag is on, we register the fragment as a core startable and this is not
+ // needed
mStatusBarInitializer.initializeStatusBar();
}
mStatusBarTouchableRegionManager.setup(getNotificationShadeWindowView());
- createNavigationBar(result);
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ createNavigationBar(result);
+ }
mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById(
R.id.ambient_indication_container);
- mAutoHideController.setStatusBar(new AutoHideUiElement() {
- @Override
- public void synchronizeState() {
- checkBarModes();
- }
+ // When the StatusBarSimpleFragment flag is enabled, all this logic will be done in
+ // StatusBarOrchestrator.
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ mAutoHideController.setStatusBar(
+ new AutoHideUiElement() {
+ @Override
+ public void synchronizeState() {
+ checkBarModes();
+ }
- @Override
- public boolean shouldHideOnTouch() {
- return !mRemoteInputManager.isRemoteInputActive();
- }
+ @Override
+ public boolean shouldHideOnTouch() {
+ return !mRemoteInputManager.isRemoteInputActive();
+ }
- @Override
- public boolean isVisible() {
- return isTransientShown();
- }
+ @Override
+ public boolean isVisible() {
+ return isTransientShown();
+ }
- @Override
- public void hide() {
- mStatusBarModeRepository.getDefaultDisplay().clearTransient();
- }
- });
+ @Override
+ public void hide() {
+ mStatusBarModeRepository.getDefaultDisplay().clearTransient();
+ }
+ });
+ }
ScrimView scrimBehind = getNotificationShadeWindowView().findViewById(R.id.scrim_behind);
ScrimView notificationsScrim = getNotificationShadeWindowView()
@@ -1479,12 +1518,14 @@
* @param state2 disable2 flags
*/
protected void setUpDisableFlags(int state1, int state2) {
+ StatusBarSimpleFragment.assertInLegacyMode();
mCommandQueue.disable(mDisplayId, state1, state2, false /* animate */);
}
// TODO(b/117478341): This was left such that CarStatusBar can override this method.
// Try to remove this.
protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
+ StatusBarSimpleFragment.assertInLegacyMode();
mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);
}
@@ -1697,14 +1738,16 @@
@Override
public void checkBarModes() {
if (mDemoModeController.isInDemoMode()) return;
- if (mStatusBarTransitions != null) {
+ // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+ // StatusBarOrchestrator.
+ if (!StatusBarSimpleFragment.isEnabled() && mStatusBarTransitions != null) {
checkBarMode(
mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode().getValue(),
mStatusBarWindowState,
mStatusBarTransitions);
+ mNoAnimationOnNextBarModeChange = false;
}
mNavigationBarController.checkNavBarModes(mDisplayId);
- mNoAnimationOnNextBarModeChange = false;
}
/** Temporarily hides Bubbles if the status bar is hidden. */
@@ -1728,7 +1771,9 @@
}
private void finishBarAnimations() {
- if (mStatusBarTransitions != null) {
+ // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+ // StatusBarOrchestrator.
+ if (!StatusBarSimpleFragment.isEnabled() && mStatusBarTransitions != null) {
mStatusBarTransitions.finishAnimations();
}
mNavigationBarController.finishBarAnimations(mDisplayId);
@@ -1770,14 +1815,17 @@
}
pw.print(" mInteractingWindows="); pw.println(mInteractingWindows);
- pw.print(" mStatusBarWindowState=");
- pw.println(windowStateToString(mStatusBarWindowState));
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ pw.print(" mStatusBarWindowState=");
+ pw.println(windowStateToString(mStatusBarWindowState));
+ }
pw.print(" mDozing="); pw.println(mDozing);
pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported);
- CentralSurfaces.dumpBarTransitions(
- pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
-
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ CentralSurfaces.dumpBarTransitions(
+ pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
+ }
pw.println(" mMediaManager: ");
if (mMediaManager != null) {
mMediaManager.dump(pw, args);
@@ -1850,7 +1898,11 @@
private void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
makeStatusBarView(result);
mNotificationShadeWindowController.attach();
- mStatusBarWindowController.attach();
+ // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+ // StatusBarOrchestrator
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ mStatusBarWindowController.attach();
+ }
}
// called by makeStatusbar and also by PhoneStatusBarView
@@ -2475,7 +2527,7 @@
int importance = bouncerShowing
? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
: IMPORTANT_FOR_ACCESSIBILITY_AUTO;
- if (mPhoneStatusBarViewController != null) {
+ if (!StatusBarSimpleFragment.isEnabled() && mPhoneStatusBarViewController != null) {
mPhoneStatusBarViewController.setImportantForAccessibility(importance);
}
mShadeSurface.setImportantForAccessibility(importance);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index ee96195..93db2db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -20,6 +20,7 @@
import static android.service.notification.NotificationListenerService.REASON_CLICK;
import static com.android.systemui.statusbar.phone.CentralSurfaces.getActivityOptions;
+import static com.android.systemui.util.kotlin.NullabilityKt.expectNotNull;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -112,6 +113,8 @@
boolean showOverTheLockScreen);
}
+ private final static String TAG = "StatusBarNotificationActivityStarter";
+
private final Context mContext;
private final int mDisplayId;
@@ -229,6 +232,7 @@
*/
@Override
public void onNotificationBubbleIconClicked(NotificationEntry entry) {
+ expectNotNull(TAG, "entry", entry);
Runnable action = () -> {
mBubblesManagerOptional.ifPresent(bubblesManager ->
bubblesManager.onUserChangedBubble(entry, !entry.isBubble()));
@@ -255,6 +259,8 @@
*/
@Override
public void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row) {
+ expectNotNull(TAG, "entry", entry);
+ expectNotNull(TAG, "row", row);
mLogger.logStartingActivityFromClick(entry, row.isHeadsUpState(),
mKeyguardStateController.isVisible(),
mNotificationShadeWindowController.getPanelExpanded());
@@ -437,6 +443,7 @@
*/
@Override
public void onDragSuccess(NotificationEntry entry) {
+ expectNotNull(TAG, "entry", entry);
// this method is not responsible for intent sending.
// will focus follow operation only after drag-and-drop that notification.
final NotificationVisibility nv = mVisibilityProvider.obtain(entry, true);
@@ -529,6 +536,8 @@
@Override
public void startNotificationGutsIntent(final Intent intent, final int appUid,
ExpandableNotificationRow row) {
+ expectNotNull(TAG, "intent", intent);
+ expectNotNull(TAG, "row", row);
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 13b651e8..5b03198 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -16,10 +16,20 @@
package com.android.systemui.statusbar.phone.dagger
import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.core.CommandQueueInitializer
import com.android.systemui.statusbar.core.StatusBarInitializer
import com.android.systemui.statusbar.core.StatusBarInitializerImpl
+import com.android.systemui.statusbar.core.StatusBarOrchestrator
+import com.android.systemui.statusbar.core.StatusBarSimpleFragment
+import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
+import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
+import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStoreImpl
import dagger.Binds
+import dagger.Lazy
import dagger.Module
+import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@@ -27,6 +37,16 @@
@Module
interface StatusBarPhoneModule {
+ @Binds
+ abstract fun windowStateRepoStore(
+ impl: StatusBarWindowStateRepositoryStoreImpl
+ ): StatusBarWindowStateRepositoryStore
+
+ @Binds
+ abstract fun commandQCallbacks(
+ impl: CentralSurfacesCommandQueueCallbacks
+ ): CommandQueue.Callbacks
+
/** Binds {@link StatusBarInitializer} as a {@link CoreStartable}. */
@Binds
@IntoMap
@@ -34,4 +54,34 @@
fun bindStatusBarInitializer(impl: StatusBarInitializerImpl): CoreStartable
@Binds fun statusBarInitializer(impl: StatusBarInitializerImpl): StatusBarInitializer
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(StatusBarOrchestrator::class)
+ fun orchestratorCoreStartable(
+ orchestratorLazy: Lazy<StatusBarOrchestrator>
+ ): CoreStartable {
+ return if (StatusBarSimpleFragment.isEnabled) {
+ orchestratorLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(CommandQueueInitializer::class)
+ fun commandQueueInitializerCoreStartable(
+ initializerLazy: Lazy<CommandQueueInitializer>
+ ): CoreStartable {
+ return if (StatusBarSimpleFragment.isEnabled) {
+ initializerLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index 78edd39..a1d5cbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -26,6 +26,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.paneTitle
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.lifecycle.DefaultLifecycleObserver
@@ -102,7 +103,13 @@
val cachedDarkTheme = remember { isCurrentlyInDarkTheme }
PlatformTheme(isDarkTheme = cachedDarkTheme) {
AlertDialogContent(
- modifier = Modifier.semantics { testTagsAsResourceId = true },
+ modifier =
+ Modifier.semantics {
+ testTagsAsResourceId = true
+ paneTitle = dialog.context.getString(
+ R.string.accessibility_desc_quick_settings
+ )
+ },
title = {
Text(
modifier = Modifier.testTag("modes_title"),
@@ -137,7 +144,7 @@
}
activityStarter.startActivity(
ZEN_MODE_SETTINGS_INTENT,
- true /* dismissShade */,
+ /* dismissShade= */ true,
animationController,
)
}
@@ -181,7 +188,7 @@
if (animationController == null) {
currentDialog?.dismiss()
}
- activityStarter.startActivity(intent, true, /* dismissShade */ animationController)
+ activityStarter.startActivity(intent, /* dismissShade= */ true, animationController)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Nullability.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
rename to packages/SystemUI/src/com/android/systemui/util/kotlin/Nullability.kt
index 298dacd..1c760be 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Nullability.kt
@@ -16,6 +16,7 @@
package com.android.systemui.util.kotlin
+import android.util.Log
import java.util.Optional
/**
@@ -28,3 +29,14 @@
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T> Optional<T>.getOrNull(): T? = orElse(null)
+
+/**
+ * Utility method to check if a value that is technically nullable is actually null. If it is null,
+ * this will crash development builds (but just log on production/droidfood builds). It can be used
+ * as a first step to verify if a nullable value can be made non-nullable instead.
+ */
+fun <T> expectNotNull(logTag: String, name: String, nullable: T?) {
+ if (nullable == null) {
+ Log.wtf(logTag, "Expected value of $name to not be null.")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index aa8c6b7..e160ff1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.res.Configuration;
@@ -643,6 +644,46 @@
environment.verifyInputSessionDispose();
}
+ @Test
+ public void testSessionPopAfterDestroy() {
+ final TouchHandler touchHandler = createTouchHandler();
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)), mKosmos);
+
+ final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(initialEvent);
+
+ // Ensure session started
+ final InputChannelCompat.InputEventListener eventListener =
+ registerInputEventListener(touchHandler);
+
+ // First event will be missed since we register after the execution loop,
+ final InputEvent event = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(event);
+ verify(eventListener).onInputEvent(eq(event));
+
+ final ArgumentCaptor<TouchHandler.TouchSession> touchSessionArgumentCaptor =
+ ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
+
+ verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
+
+ environment.updateLifecycle(Lifecycle.State.DESTROYED);
+
+ // Check to make sure the input session is now disposed.
+ environment.verifyInputSessionDispose();
+
+ clearInvocations(environment.mInputFactory);
+
+ // Pop the session
+ touchSessionArgumentCaptor.getValue().pop();
+
+ environment.executeAll();
+
+ // Ensure no input sessions were created due to the session reset.
+ verifyNoMoreInteractions(environment.mInputFactory);
+ }
+
@Test
public void testPilfering() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index b0810a9..6608542 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -100,6 +100,7 @@
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -199,6 +200,7 @@
private @Mock ShadeWindowLogger mShadeWindowLogger;
private @Mock SelectedUserInteractor mSelectedUserInteractor;
private @Mock KeyguardInteractor mKeyguardInteractor;
+ private @Mock KeyguardTransitionBootInteractor mKeyguardTransitionBootInteractor;
private @Captor ArgumentCaptor<KeyguardStateController.Callback>
mKeyguardStateControllerCallback;
private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -1294,6 +1296,7 @@
() -> mock(WindowManagerLockscreenVisibilityManager.class),
mSelectedUserInteractor,
mKeyguardInteractor,
+ mKeyguardTransitionBootInteractor,
mock(WindowManagerOcclusionManager.class));
mViewMediator.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
index 8a5af09..ad5eeab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
@@ -65,7 +65,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
private val goneToLs =
@@ -75,7 +75,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
@Before
@@ -84,7 +84,8 @@
kosmos.sceneContainerRepository.setTransitionState(sceneTransitions)
testScope.launch {
kosmos.realKeyguardTransitionRepository.emitInitialStepsFromOff(
- KeyguardState.LOCKSCREEN
+ KeyguardState.LOCKSCREEN,
+ testSetup = true,
)
}
}
@@ -105,11 +106,7 @@
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
assertTransition(
@@ -142,11 +139,7 @@
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
@@ -191,7 +184,7 @@
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
sceneTransitions.value = lsToGone
@@ -205,11 +198,7 @@
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
@@ -257,11 +246,7 @@
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
@@ -297,7 +282,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -330,11 +315,7 @@
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
val stepM3 = allSteps[allSteps.size - 3]
@@ -393,7 +374,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -466,7 +447,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -523,7 +504,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -577,11 +558,7 @@
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
kosmos.realKeyguardTransitionRepository.startTransition(
TransitionInfo(
@@ -589,7 +566,7 @@
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -641,11 +618,7 @@
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
kosmos.realKeyguardTransitionRepository.startTransition(
TransitionInfo(
@@ -653,7 +626,7 @@
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -702,11 +675,7 @@
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
kosmos.realKeyguardTransitionRepository.startTransition(
TransitionInfo(
@@ -714,7 +683,7 @@
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -736,7 +705,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -777,7 +746,7 @@
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -799,7 +768,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
allSteps[allSteps.size - 3]
@@ -858,7 +827,7 @@
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -880,7 +849,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -959,7 +928,7 @@
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -977,7 +946,7 @@
from = KeyguardState.AOD,
to = KeyguardState.DOZING,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -995,7 +964,7 @@
from = KeyguardState.DOZING,
to = KeyguardState.OCCLUDED,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -1017,7 +986,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -1077,7 +1046,7 @@
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -1092,7 +1061,7 @@
kosmos.realKeyguardTransitionRepository.updateTransition(
ktfUuid!!,
1f,
- TransitionState.FINISHED
+ TransitionState.FINISHED,
)
assertTransition(
@@ -1110,7 +1079,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -1171,7 +1140,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -1235,7 +1204,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -1291,7 +1260,7 @@
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -1308,7 +1277,7 @@
from: KeyguardState? = null,
to: KeyguardState? = null,
state: TransitionState? = null,
- progress: Float? = null
+ progress: Float? = null,
) {
if (from != null) assertThat(step.from).isEqualTo(from)
if (to != null) assertThat(step.to).isEqualTo(to)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index d32d8cc..fb376ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -1890,7 +1890,7 @@
// Callback gets an updated state
val state = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
- stateCallbackCaptor.value.invoke(KEY, state)
+ onStateUpdated(KEY, state)
// Listener is notified of updated state
verify(listener)
@@ -1911,7 +1911,7 @@
// No media added with this key
- stateCallbackCaptor.value.invoke(KEY, state)
+ onStateUpdated(KEY, state)
verify(listener, never())
.onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -1928,7 +1928,7 @@
val state = PlaybackState.Builder().build()
// Then no changes are made
- stateCallbackCaptor.value.invoke(KEY, state)
+ onStateUpdated(KEY, state)
verify(listener, never())
.onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -1939,7 +1939,7 @@
whenever(controller.playbackState).thenReturn(state)
addNotificationAndLoad()
- stateCallbackCaptor.value.invoke(KEY, state)
+ onStateUpdated(KEY, state)
verify(listener)
.onMediaDataLoaded(
@@ -1983,7 +1983,7 @@
backgroundExecutor.runAllReady()
foregroundExecutor.runAllReady()
- stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
+ onStateUpdated(PACKAGE_NAME, state)
verify(listener)
.onMediaDataLoaded(
@@ -2008,7 +2008,7 @@
.build()
addNotificationAndLoad()
- stateCallbackCaptor.value.invoke(KEY, state)
+ onStateUpdated(KEY, state)
verify(listener)
.onMediaDataLoaded(
@@ -2518,4 +2518,10 @@
eq(false),
)
}
+
+ private fun onStateUpdated(key: String, state: PlaybackState) {
+ stateCallbackCaptor.value.invoke(key, state)
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 90af932..7d364bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -1967,7 +1967,7 @@
// Callback gets an updated state
val state = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
- stateCallbackCaptor.value.invoke(KEY, state)
+ testScope.onStateUpdated(KEY, state)
// Listener is notified of updated state
verify(listener)
@@ -1988,7 +1988,7 @@
// No media added with this key
- stateCallbackCaptor.value.invoke(KEY, state)
+ testScope.onStateUpdated(KEY, state)
verify(listener, never())
.onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2005,7 +2005,7 @@
val state = PlaybackState.Builder().build()
// Then no changes are made
- stateCallbackCaptor.value.invoke(KEY, state)
+ testScope.onStateUpdated(KEY, state)
verify(listener, never())
.onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2016,7 +2016,7 @@
whenever(controller.playbackState).thenReturn(state)
addNotificationAndLoad()
- stateCallbackCaptor.value.invoke(KEY, state)
+ testScope.onStateUpdated(KEY, state)
verify(listener)
.onMediaDataLoaded(
@@ -2059,7 +2059,7 @@
backgroundExecutor.runAllReady()
foregroundExecutor.runAllReady()
- stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
+ testScope.onStateUpdated(PACKAGE_NAME, state)
verify(listener)
.onMediaDataLoaded(
@@ -2084,7 +2084,7 @@
.build()
addNotificationAndLoad()
- stateCallbackCaptor.value.invoke(KEY, state)
+ testScope.onStateUpdated(KEY, state)
verify(listener)
.onMediaDataLoaded(
@@ -2603,4 +2603,11 @@
eq(false),
)
}
+
+ /** Helper function to update state and run executors */
+ private fun TestScope.onStateUpdated(key: String, state: PlaybackState) {
+ stateCallbackCaptor.value.invoke(key, state)
+ runCurrent()
+ advanceUntilIdle()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 03667cf..570c640 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -21,19 +21,19 @@
import android.content.res.Configuration
import android.database.ContentObserver
import android.os.LocaleList
+import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import android.testing.TestableLooper
import android.util.MathUtils.abs
import android.view.View
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Flags.mediaControlsUmoInflationInBackground
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.DisableSceneContainer
@@ -71,7 +71,6 @@
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.testKosmos
-import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.unconfinedDispatcherFakeSettings
@@ -106,6 +105,8 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.capture
import org.mockito.kotlin.eq
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
private val DATA = MediaTestUtils.emptyMediaData
@@ -116,8 +117,8 @@
@ExperimentalCoroutinesApi
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidJUnit4::class)
-class MediaCarouselControllerTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class MediaCarouselControllerTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testDispatcher = kosmos.unconfinedTestDispatcher
private val secureSettings = kosmos.unconfinedDispatcherFakeSettings
@@ -129,7 +130,6 @@
@Mock lateinit var mediaHostStatesManager: MediaHostStatesManager
@Mock lateinit var mediaHostState: MediaHostState
@Mock lateinit var activityStarter: ActivityStarter
- @Mock @Main private lateinit var executor: DelayableExecutor
@Mock lateinit var mediaDataManager: MediaDataManager
@Mock lateinit var configurationController: ConfigurationController
@Mock lateinit var falsingManager: FalsingManager
@@ -153,16 +153,33 @@
private val clock = FakeSystemClock()
private lateinit var bgExecutor: FakeExecutor
+ private lateinit var uiExecutor: FakeExecutor
private lateinit var mediaCarouselController: MediaCarouselController
private var originalResumeSetting =
Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.progressionOf(
+ com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_UMO_INFLATION_IN_BACKGROUND
+ )
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK))
bgExecutor = FakeExecutor(clock)
+ uiExecutor = FakeExecutor(clock)
+
mediaCarouselController =
MediaCarouselController(
applicationScope = kosmos.applicationCoroutineScope,
@@ -173,7 +190,7 @@
activityStarter = activityStarter,
systemClock = clock,
mainDispatcher = kosmos.testDispatcher,
- executor = executor,
+ uiExecutor = uiExecutor,
bgExecutor = bgExecutor,
backgroundDispatcher = testDispatcher,
mediaManager = mediaDataManager,
@@ -201,10 +218,11 @@
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
MediaPlayerData.clear()
FakeExecutor.exhaustExecutors(bgExecutor)
+ FakeExecutor.exhaustExecutors(uiExecutor)
verify(globalSettings)
.registerContentObserverSync(
eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)),
- capture(settingsObserverCaptor)
+ capture(settingsObserverCaptor),
)
}
@@ -213,7 +231,7 @@
Settings.Secure.putInt(
context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RESUME,
- originalResumeSetting
+ originalResumeSetting,
)
}
@@ -227,9 +245,9 @@
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
+ resumption = false,
),
- 4500L
+ 4500L,
)
val playingCast =
@@ -239,9 +257,9 @@
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_CAST_LOCAL,
- resumption = false
+ resumption = false,
),
- 5000L
+ 5000L,
)
val pausedLocal =
@@ -251,9 +269,9 @@
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
+ resumption = false,
),
- 1000L
+ 1000L,
)
val pausedCast =
@@ -263,9 +281,9 @@
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_CAST_LOCAL,
- resumption = false
+ resumption = false,
),
- 2000L
+ 2000L,
)
val playingRcn =
@@ -275,9 +293,9 @@
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_CAST_REMOTE,
- resumption = false
+ resumption = false,
),
- 5000L
+ 5000L,
)
val pausedRcn =
@@ -287,9 +305,9 @@
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_CAST_REMOTE,
- resumption = false
+ resumption = false,
),
- 5000L
+ 5000L,
)
val active =
@@ -299,9 +317,9 @@
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = true
+ resumption = true,
),
- 250L
+ 250L,
)
val resume1 =
@@ -311,9 +329,9 @@
active = false,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = true
+ resumption = true,
),
- 500L
+ 500L,
)
val resume2 =
@@ -323,9 +341,9 @@
active = false,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = true
+ resumption = true,
),
- 1000L
+ 1000L,
)
val activeMoreRecent =
@@ -336,9 +354,9 @@
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
resumption = true,
- lastActive = 2L
+ lastActive = 2L,
),
- 1000L
+ 1000L,
)
val activeLessRecent =
@@ -349,9 +367,9 @@
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
resumption = true,
- lastActive = 1L
+ lastActive = 1L,
),
- 1000L
+ 1000L,
)
// Expected ordering for media players:
// Actively playing local sessions
@@ -370,7 +388,7 @@
pausedRcn,
active,
resume2,
- resume1
+ resume1,
)
expected.forEach {
@@ -380,7 +398,7 @@
it.second.copy(notificationKey = it.first),
panel,
clock,
- isSsReactivated = false
+ isSsReactivated = false,
)
}
@@ -403,7 +421,7 @@
EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
panel,
true,
- clock
+ clock,
)
// Then it should be shown immediately after any actively playing controls
@@ -421,7 +439,7 @@
listener.value.onSmartspaceMediaDataLoaded(
SMARTSPACE_KEY,
EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
- true
+ true,
)
// Then it should be shown immediately after any actively playing controls
@@ -439,7 +457,7 @@
EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
panel,
false,
- clock
+ clock,
)
// Then it should be shown at the end of the carousel's active entries
@@ -461,8 +479,8 @@
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
+ resumption = false,
+ ),
)
listener.value.onMediaDataLoaded(
PLAYING_LOCAL,
@@ -471,19 +489,20 @@
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = true
- )
+ resumption = true,
+ ),
)
+ runAllReady()
assertEquals(
MediaPlayerData.getMediaPlayerIndex(PAUSED_LOCAL),
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex,
)
// paused player order should stays the same in visibleMediaPLayer map.
// paused player order should be first in mediaPlayer map.
assertEquals(
MediaPlayerData.visiblePlayerKeys().elementAt(3),
- MediaPlayerData.playerKeys().elementAt(0)
+ MediaPlayerData.playerKeys().elementAt(0),
)
}
@@ -506,7 +525,7 @@
mediaCarouselController.onDesiredLocationChanged(
LOCATION_QS,
mediaHostState,
- animate = false
+ animate = false,
)
bgExecutor.runAllReady()
verify(logger).logCarouselPosition(LOCATION_QS)
@@ -517,7 +536,7 @@
mediaCarouselController.onDesiredLocationChanged(
MediaHierarchyManager.LOCATION_QQS,
mediaHostState,
- animate = false
+ animate = false,
)
bgExecutor.runAllReady()
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
@@ -528,7 +547,7 @@
mediaCarouselController.onDesiredLocationChanged(
MediaHierarchyManager.LOCATION_LOCKSCREEN,
mediaHostState,
- animate = false
+ animate = false,
)
bgExecutor.runAllReady()
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
@@ -539,7 +558,7 @@
mediaCarouselController.onDesiredLocationChanged(
MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
mediaHostState,
- animate = false
+ animate = false,
)
bgExecutor.runAllReady()
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
@@ -570,8 +589,8 @@
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
+ resumption = false,
+ ),
)
listener.value.onMediaDataLoaded(
PAUSED_LOCAL,
@@ -580,14 +599,15 @@
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
+ resumption = false,
+ ),
)
+ runAllReady()
// adding a media recommendation card.
listener.value.onSmartspaceMediaDataLoaded(
SMARTSPACE_KEY,
EMPTY_SMARTSPACE_MEDIA_DATA,
- false
+ false,
)
mediaCarouselController.shouldScrollToKey = true
// switching between media players.
@@ -598,8 +618,8 @@
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = true
- )
+ resumption = true,
+ ),
)
listener.value.onMediaDataLoaded(
PAUSED_LOCAL,
@@ -608,13 +628,14 @@
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
+ resumption = false,
+ ),
)
+ runAllReady()
assertEquals(
MediaPlayerData.getMediaPlayerIndex(PAUSED_LOCAL),
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex,
)
}
@@ -626,7 +647,7 @@
listener.value.onSmartspaceMediaDataLoaded(
SMARTSPACE_KEY,
EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true),
- false
+ false,
)
listener.value.onMediaDataLoaded(
PLAYING_LOCAL,
@@ -635,14 +656,15 @@
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
+ resumption = false,
+ ),
)
+ runAllReady()
var playerIndex = MediaPlayerData.getMediaPlayerIndex(PLAYING_LOCAL)
assertEquals(
playerIndex,
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex,
)
assertEquals(playerIndex, 0)
@@ -657,9 +679,10 @@
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_LOCAL,
resumption = false,
- packageName = "PACKAGE_NAME"
- )
+ packageName = "PACKAGE_NAME",
+ ),
)
+ runAllReady()
playerIndex = MediaPlayerData.getMediaPlayerIndex(PLAYING_LOCAL)
assertEquals(playerIndex, 0)
}
@@ -704,7 +727,7 @@
player1.second.copy(notificationKey = player1.first),
panel,
clock,
- isSsReactivated = false
+ isSsReactivated = false,
)
assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1)
@@ -717,7 +740,7 @@
player2.second.copy(notificationKey = player2.first),
panel,
clock,
- isSsReactivated = false
+ isSsReactivated = false,
)
// mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
@@ -732,7 +755,7 @@
player3.second.copy(notificationKey = player3.first),
panel,
clock,
- isSsReactivated = false
+ isSsReactivated = false,
)
// mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
@@ -822,7 +845,7 @@
listener.value.onSmartspaceMediaDataLoaded(
SMARTSPACE_KEY,
EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
- true
+ true,
)
// Then the carousel is updated
@@ -841,7 +864,7 @@
listener.value.onSmartspaceMediaDataLoaded(
SMARTSPACE_KEY,
EMPTY_SMARTSPACE_MEDIA_DATA,
- false
+ false,
)
// Then it is added to the carousel with correct state
@@ -886,7 +909,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
- this
+ this,
)
verify(mediaCarousel).visibility = View.VISIBLE
@@ -932,7 +955,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
- this
+ this,
)
assertEquals(true, updatedVisibility)
@@ -961,7 +984,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
- this
+ this,
)
assertEquals(true, updatedVisibility)
@@ -1125,12 +1148,14 @@
Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
val pausedMedia = DATA.copy(isPlaying = false)
listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
+ runAllReady()
mediaCarouselController.onSwipeToDismiss()
// When it can be removed immediately on update
whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true)
val inactiveMedia = pausedMedia.copy(active = false)
listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia)
+ runAllReady()
// This is processed as a user-initiated dismissal
verify(debugLogger).logMediaRemoved(eq(PAUSED_LOCAL), eq(true))
@@ -1148,12 +1173,14 @@
val pausedMedia = DATA.copy(isPlaying = false)
listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
+ runAllReady()
mediaCarouselController.onSwipeToDismiss()
// When it can't be removed immediately on update
whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false)
val inactiveMedia = pausedMedia.copy(active = false)
listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia)
+ runAllReady()
visualStabilityCallback.value.onReorderingAllowed()
// This is processed as a user-initiated dismissal
@@ -1175,8 +1202,8 @@
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
+ resumption = false,
+ ),
)
listener.value.onMediaDataLoaded(
PAUSED_LOCAL,
@@ -1185,18 +1212,20 @@
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
+ resumption = false,
+ ),
)
+ runAllReady()
val playersSize = MediaPlayerData.players().size
reset(pageIndicator)
function()
+ runAllReady()
assertEquals(playersSize, MediaPlayerData.players().size)
assertEquals(
MediaPlayerData.getMediaPlayerIndex(PLAYING_LOCAL),
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex,
)
}
@@ -1225,4 +1254,11 @@
)
runCurrent()
}
+
+ private fun runAllReady() {
+ if (mediaControlsUmoInflationInBackground()) {
+ bgExecutor.runAllReady()
+ uiExecutor.runAllReady()
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
new file mode 100644
index 0000000..2a196c6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.core
+
+import android.internal.statusbar.fakeStatusBarService
+import android.platform.test.annotations.EnableFlags
+import android.view.WindowInsets
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.initController
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.mockCommandQueueCallbacks
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.verify
+
+@EnableFlags(StatusBarSimpleFragment.FLAG_NAME)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommandQueueInitializerTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val initController = kosmos.initController
+ private val commandQueue = kosmos.fakeCommandQueue
+ private val commandQueueCallbacks = kosmos.mockCommandQueueCallbacks
+ private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository
+ private val fakeStatusBarService = kosmos.fakeStatusBarService
+ private val initializer = kosmos.commandQueueInitializer
+
+ @Test
+ fun start_registersStatusBar() {
+ initializer.start()
+
+ assertThat(fakeStatusBarService.registeredStatusBar).isNotNull()
+ }
+
+ @Test
+ fun start_barResultHasTransientStatusBar_transientStateIsTrue() {
+ fakeStatusBarService.transientBarTypes = WindowInsets.Type.statusBars()
+
+ initializer.start()
+
+ assertThat(statusBarModeRepository.defaultDisplay.isTransientShown.value).isTrue()
+ }
+
+ @Test
+ fun start_barResultDoesNotHaveTransientStatusBar_transientStateIsFalse() {
+ fakeStatusBarService.transientBarTypes = WindowInsets.Type.navigationBars()
+
+ initializer.start()
+
+ assertThat(statusBarModeRepository.defaultDisplay.isTransientShown.value).isFalse()
+ }
+
+ @Test
+ fun start_callsOnSystemBarAttributesChanged_basedOnRegisterBarResult() {
+ initializer.start()
+
+ verify(commandQueueCallbacks)
+ .onSystemBarAttributesChanged(
+ context.displayId,
+ fakeStatusBarService.appearance,
+ fakeStatusBarService.appearanceRegions,
+ fakeStatusBarService.navbarColorManagedByIme,
+ fakeStatusBarService.behavior,
+ fakeStatusBarService.requestedVisibleTypes,
+ fakeStatusBarService.packageName,
+ fakeStatusBarService.letterboxDetails,
+ )
+ }
+
+ @Test
+ fun start_callsSetIcon_basedOnRegisterBarResult() {
+ initializer.start()
+
+ assertThat(commandQueue.icons).isEqualTo(fakeStatusBarService.statusBarIcons)
+ }
+
+ @Test
+ fun start_callsSetImeWindowStatus_basedOnRegisterBarResult() {
+ initializer.start()
+
+ verify(commandQueueCallbacks)
+ .setImeWindowStatus(
+ context.displayId,
+ fakeStatusBarService.imeWindowVis,
+ fakeStatusBarService.imeBackDisposition,
+ fakeStatusBarService.showImeSwitcher,
+ )
+ }
+
+ @Test
+ fun start_afterPostInitTaskExecuted_callsDisableFlags_basedOnRegisterBarResult() {
+ initializer.start()
+
+ initController.executePostInitTasks()
+
+ assertThat(commandQueue.disableFlags1ForDisplay(context.displayId))
+ .isEqualTo(fakeStatusBarService.disabledFlags1)
+ assertThat(commandQueue.disableFlags2ForDisplay(context.displayId))
+ .isEqualTo(fakeStatusBarService.disabledFlags2)
+ }
+
+ @Test
+ fun start_beforePostInitTaskExecuted_doesNotCallsDisableFlags() {
+ initializer.start()
+
+ assertThat(commandQueue.disableFlags1ForDisplay(context.displayId)).isNull()
+ assertThat(commandQueue.disableFlags2ForDisplay(context.displayId)).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
new file mode 100644
index 0000000..5803365
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.core
+
+import android.platform.test.annotations.EnableFlags
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.unconfinedTestDispatcher
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.plugins.mockPluginDependencyProvider
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.shade.mockNotificationShadeWindowViewController
+import com.android.systemui.shade.mockShadeSurface
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.data.model.StatusBarMode.LIGHTS_OUT
+import com.android.systemui.statusbar.data.model.StatusBarMode.LIGHTS_OUT_TRANSPARENT
+import com.android.systemui.statusbar.data.model.StatusBarMode.OPAQUE
+import com.android.systemui.statusbar.data.model.StatusBarMode.TRANSPARENT
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.phone.mockPhoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.mockPhoneStatusBarViewController
+import com.android.systemui.statusbar.window.data.model.StatusBarWindowState
+import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStateRepositoryStore
+import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore
+import com.android.systemui.statusbar.window.fakeStatusBarWindowController
+import com.android.systemui.testKosmos
+import com.android.wm.shell.bubbles.bubbles
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+@EnableFlags(StatusBarSimpleFragment.FLAG_NAME)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StatusBarOrchestratorTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().also {
+ it.testDispatcher = it.unconfinedTestDispatcher
+ it.statusBarWindowStateRepositoryStore = it.fakeStatusBarWindowStateRepositoryStore
+ }
+ private val testScope = kosmos.testScope
+ private val statusBarViewController = kosmos.mockPhoneStatusBarViewController
+ private val statusBarWindowController = kosmos.fakeStatusBarWindowController
+ private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository
+ private val pluginDependencyProvider = kosmos.mockPluginDependencyProvider
+ private val notificationShadeWindowViewController =
+ kosmos.mockNotificationShadeWindowViewController
+ private val shadeSurface = kosmos.mockShadeSurface
+ private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ private val fakeStatusBarWindowStateRepositoryStore =
+ kosmos.fakeStatusBarWindowStateRepositoryStore
+ private val fakePowerRepository = kosmos.fakePowerRepository
+ private val mockPhoneStatusBarTransitions = kosmos.mockPhoneStatusBarTransitions
+ private val mockBubbles = kosmos.bubbles
+
+ private val orchestrator = kosmos.statusBarOrchestrator
+
+ @Test
+ fun start_setsUpPluginDependencies() {
+ orchestrator.start()
+
+ verify(pluginDependencyProvider).allowPluginDependency(DarkIconDispatcher::class.java)
+ verify(pluginDependencyProvider).allowPluginDependency(StatusBarStateController::class.java)
+ }
+
+ @Test
+ fun start_attachesWindow() {
+ orchestrator.start()
+
+ assertThat(statusBarWindowController.isAttached).isTrue()
+ }
+
+ @Test
+ fun start_setsStatusBarControllerOnShade() {
+ orchestrator.start()
+
+ verify(notificationShadeWindowViewController)
+ .setStatusBarViewController(statusBarViewController)
+ }
+
+ @Test
+ fun start_updatesShadeExpansion() {
+ orchestrator.start()
+
+ verify(shadeSurface).updateExpansionAndVisibility()
+ }
+
+ @Test
+ fun bouncerShowing_setsImportanceForA11yToNoHideDescendants() =
+ testScope.runTest {
+ orchestrator.start()
+
+ bouncerRepository.setPrimaryShow(isShowing = true)
+
+ verify(statusBarViewController)
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
+ }
+
+ @Test
+ fun bouncerNotShowing_setsImportanceForA11yToNoHideDescendants() =
+ testScope.runTest {
+ orchestrator.start()
+
+ bouncerRepository.setPrimaryShow(isShowing = false)
+
+ verify(statusBarViewController)
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO)
+ }
+
+ @Test
+ fun deviceGoesToSleep_barTransitionsAnimationsAreFinished() =
+ testScope.runTest {
+ putDeviceToSleep()
+
+ orchestrator.start()
+
+ verify(mockPhoneStatusBarTransitions).finishAnimations()
+ }
+
+ @Test
+ fun deviceIsAwake_barTransitionsAnimationsAreNotFinished() =
+ testScope.runTest {
+ awakeDevice()
+
+ orchestrator.start()
+
+ verify(mockPhoneStatusBarTransitions, never()).finishAnimations()
+ }
+
+ @Test
+ fun statusBarVisible_notifiesBubbles() =
+ testScope.runTest {
+ setStatusBarMode(TRANSPARENT)
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+
+ orchestrator.start()
+
+ verify(mockBubbles).onStatusBarVisibilityChanged(/* visible= */ true)
+ }
+
+ @Test
+ fun statusBarInLightsOutMode_notifiesBubblesWithStatusBarInvisible() =
+ testScope.runTest {
+ setStatusBarMode(LIGHTS_OUT)
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+
+ orchestrator.start()
+
+ verify(mockBubbles).onStatusBarVisibilityChanged(/* visible= */ false)
+ }
+
+ @Test
+ fun statusBarInLightsOutTransparentMode_notifiesBubblesWithStatusBarInvisible() =
+ testScope.runTest {
+ setStatusBarMode(LIGHTS_OUT_TRANSPARENT)
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+
+ orchestrator.start()
+
+ verify(mockBubbles).onStatusBarVisibilityChanged(/* visible= */ false)
+ }
+
+ @Test
+ fun statusBarWindowNotShowing_notifiesBubblesWithStatusBarInvisible() =
+ testScope.runTest {
+ setStatusBarMode(TRANSPARENT)
+ setStatusBarWindowState(StatusBarWindowState.Hidden)
+
+ orchestrator.start()
+
+ verify(mockBubbles).onStatusBarVisibilityChanged(/* visible= */ false)
+ }
+
+ @Test
+ fun statusBarModeChange_transitionsToModeWithAnimation() =
+ testScope.runTest {
+ awakeDevice()
+ clearTransientStatusBar()
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+ setStatusBarMode(TRANSPARENT)
+
+ orchestrator.start()
+
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
+ }
+
+ @Test
+ fun statusBarModeChange_keepsTransitioningAsModeChanges() =
+ testScope.runTest {
+ awakeDevice()
+ clearTransientStatusBar()
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+ setStatusBarMode(TRANSPARENT)
+
+ orchestrator.start()
+
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
+
+ setStatusBarMode(OPAQUE)
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(OPAQUE.toTransitionModeInt(), /* animate= */ true)
+
+ setStatusBarMode(LIGHTS_OUT)
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(LIGHTS_OUT.toTransitionModeInt(), /* animate= */ true)
+
+ setStatusBarMode(LIGHTS_OUT_TRANSPARENT)
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(LIGHTS_OUT_TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
+ }
+
+ @Test
+ fun statusBarModeChange_transientIsShown_transitionsToModeWithoutAnimation() =
+ testScope.runTest {
+ awakeDevice()
+ setTransientStatusBar()
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+ setStatusBarMode(TRANSPARENT)
+
+ orchestrator.start()
+
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
+ }
+
+ @Test
+ fun statusBarModeChange_windowIsHidden_transitionsToModeWithoutAnimation() =
+ testScope.runTest {
+ awakeDevice()
+ clearTransientStatusBar()
+ setStatusBarWindowState(StatusBarWindowState.Hidden)
+ setStatusBarMode(TRANSPARENT)
+
+ orchestrator.start()
+
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
+ }
+
+ @Test
+ fun statusBarModeChange_deviceIsAsleep_transitionsToModeWithoutAnimation() =
+ testScope.runTest {
+ putDeviceToSleep()
+ clearTransientStatusBar()
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+ setStatusBarMode(TRANSPARENT)
+
+ orchestrator.start()
+
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
+ }
+
+ @Test
+ fun statusBarModeAnimationConditionsChange_withoutBarModeChange_noNewTransitionsHappen() =
+ testScope.runTest {
+ awakeDevice()
+ clearTransientStatusBar()
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+ setStatusBarMode(TRANSPARENT)
+
+ orchestrator.start()
+
+ putDeviceToSleep()
+ awakeDevice()
+ setTransientStatusBar()
+ clearTransientStatusBar()
+
+ verify(mockPhoneStatusBarTransitions, times(1))
+ .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
+ }
+
+ private fun putDeviceToSleep() {
+ fakePowerRepository.updateWakefulness(
+ rawState = WakefulnessState.ASLEEP,
+ lastWakeReason = WakeSleepReason.KEY,
+ lastSleepReason = WakeSleepReason.KEY,
+ powerButtonLaunchGestureTriggered = true,
+ )
+ }
+
+ private fun awakeDevice() {
+ fakePowerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.KEY,
+ lastSleepReason = WakeSleepReason.KEY,
+ powerButtonLaunchGestureTriggered = true,
+ )
+ }
+
+ private fun setTransientStatusBar() {
+ statusBarModeRepository.defaultDisplay.showTransient()
+ }
+
+ private fun clearTransientStatusBar() {
+ statusBarModeRepository.defaultDisplay.clearTransient()
+ }
+
+ private fun setStatusBarWindowState(state: StatusBarWindowState) {
+ fakeStatusBarWindowStateRepositoryStore.defaultDisplay.setWindowState(state)
+ }
+
+ private fun setStatusBarMode(statusBarMode: StatusBarMode) {
+ statusBarModeRepository.defaultDisplay.statusBarMode.value = statusBarMode
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index cea8857..7d5278e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -332,7 +332,10 @@
// WHEN a row is pinned
headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
// AND the lock screen is shown
- keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN)
+ keyguardTransitionRepository.emitInitialStepsFromOff(
+ to = KeyguardState.LOCKSCREEN,
+ testSetup = true,
+ )
assertThat(showHeadsUpStatusBar).isFalse()
}
@@ -345,7 +348,10 @@
// WHEN a row is pinned
headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
// AND the lock screen is shown
- keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN)
+ keyguardTransitionRepository.emitInitialStepsFromOff(
+ to = KeyguardState.LOCKSCREEN,
+ testSetup = true,
+ )
// AND bypass is enabled
faceAuthRepository.isBypassEnabled.value = true
@@ -359,7 +365,10 @@
// WHEN no pinned rows
// AND the lock screen is shown
- keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN)
+ keyguardTransitionRepository.emitInitialStepsFromOff(
+ to = KeyguardState.LOCKSCREEN,
+ testSetup = true,
+ )
// AND bypass is enabled
faceAuthRepository.isBypassEnabled.value = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c710c56..15ea811 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -169,6 +169,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
+import com.android.systemui.statusbar.core.StatusBarOrchestrator;
import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -346,6 +347,7 @@
@Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
@Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
@Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
+ @Mock private StatusBarOrchestrator mStatusBarOrchestrator;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index e396b56..0598b87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -133,7 +133,10 @@
// WHEN HUN displayed on the bypass lock screen
headsUpRepository.setNotifications(FakeHeadsUpRowRepository("key 0", isPinned = true))
- keyguardTransitionRepository.emitInitialStepsFromOff(KeyguardState.LOCKSCREEN)
+ keyguardTransitionRepository.emitInitialStepsFromOff(
+ KeyguardState.LOCKSCREEN,
+ testSetup = true,
+ )
kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
faceAuthRepository.isBypassEnabled.value = true
diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
new file mode 100644
index 0000000..cc0597b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
@@ -0,0 +1,355 @@
+/*
+ * 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 android.internal.statusbar
+
+import android.app.Notification
+import android.content.ComponentName
+import android.graphics.Rect
+import android.graphics.drawable.Icon
+import android.hardware.biometrics.IBiometricContextListener
+import android.hardware.biometrics.IBiometricSysuiReceiver
+import android.hardware.biometrics.PromptInfo
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback
+import android.media.INearbyMediaDevicesProvider
+import android.media.MediaRoute2Info
+import android.net.Uri
+import android.os.Bundle
+import android.os.IBinder
+import android.os.UserHandle
+import android.util.ArrayMap
+import android.view.KeyEvent
+import com.android.internal.logging.InstanceId
+import com.android.internal.statusbar.IAddTileResultCallback
+import com.android.internal.statusbar.ISessionListener
+import com.android.internal.statusbar.IStatusBar
+import com.android.internal.statusbar.IStatusBarService
+import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.internal.statusbar.LetterboxDetails
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.internal.statusbar.RegisterStatusBarResult
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.internal.view.AppearanceRegion
+import org.mockito.kotlin.mock
+
+class FakeStatusBarService : IStatusBarService.Stub() {
+
+ var registeredStatusBar: IStatusBar? = null
+ private set
+
+ var statusBarIcons =
+ ArrayMap<String, StatusBarIcon>().also {
+ it["slot1"] = mock<StatusBarIcon>()
+ it["slot2"] = mock<StatusBarIcon>()
+ }
+ var disabledFlags1 = 1234567
+ var appearance = 123
+ var appearanceRegions =
+ arrayOf(
+ AppearanceRegion(
+ /* appearance = */ 123,
+ /* bounds = */ Rect(/* left= */ 4, /* top= */ 3, /* right= */ 2, /* bottom= */ 1),
+ ),
+ AppearanceRegion(
+ /* appearance = */ 345,
+ /* bounds = */ Rect(/* left= */ 1, /* top= */ 2, /* right= */ 3, /* bottom= */ 4),
+ ),
+ )
+ var imeWindowVis = 987
+ var imeBackDisposition = 654
+ var showImeSwitcher = true
+ var disabledFlags2 = 7654321
+ var navbarColorManagedByIme = true
+ var behavior = 234
+ var requestedVisibleTypes = 345
+ var packageName = "fake.bar.ser.vice"
+ var transientBarTypes = 0
+ var letterboxDetails =
+ arrayOf(
+ LetterboxDetails(
+ /* letterboxInnerBounds = */ Rect(
+ /* left= */ 5,
+ /* top= */ 6,
+ /* right= */ 7,
+ /* bottom= */ 8,
+ ),
+ /* letterboxFullBounds = */ Rect(
+ /* left= */ 1,
+ /* top= */ 2,
+ /* right= */ 3,
+ /* bottom= */ 4,
+ ),
+ /* appAppearance = */ 123,
+ )
+ )
+
+ override fun expandNotificationsPanel() {}
+
+ override fun collapsePanels() {}
+
+ override fun togglePanel() {}
+
+ override fun disable(what: Int, token: IBinder, pkg: String) {
+ disableForUser(what, token, pkg, userId = 0)
+ }
+
+ override fun disableForUser(what: Int, token: IBinder, pkg: String, userId: Int) {}
+
+ override fun disable2(what: Int, token: IBinder, pkg: String) {
+ disable2ForUser(what, token, pkg, userId = 0)
+ }
+
+ override fun disable2ForUser(what: Int, token: IBinder, pkg: String, userId: Int) {}
+
+ override fun getDisableFlags(token: IBinder, userId: Int): IntArray {
+ return intArrayOf(disabledFlags1, disabledFlags2)
+ }
+
+ override fun setIcon(
+ slot: String,
+ iconPackage: String,
+ iconId: Int,
+ iconLevel: Int,
+ contentDescription: String,
+ ) {}
+
+ override fun setIconVisibility(slot: String, visible: Boolean) {}
+
+ override fun removeIcon(slot: String) {}
+
+ override fun setImeWindowStatus(
+ displayId: Int,
+ vis: Int,
+ backDisposition: Int,
+ showImeSwitcher: Boolean,
+ ) {}
+
+ override fun expandSettingsPanel(subPanel: String) {}
+
+ override fun registerStatusBar(callbacks: IStatusBar): RegisterStatusBarResult {
+ registeredStatusBar = callbacks
+ return RegisterStatusBarResult(
+ statusBarIcons,
+ disabledFlags1,
+ appearance,
+ appearanceRegions,
+ imeWindowVis,
+ imeBackDisposition,
+ showImeSwitcher,
+ disabledFlags2,
+ navbarColorManagedByIme,
+ behavior,
+ requestedVisibleTypes,
+ packageName,
+ transientBarTypes,
+ letterboxDetails,
+ )
+ }
+
+ override fun onPanelRevealed(clearNotificationEffects: Boolean, numItems: Int) {}
+
+ override fun onPanelHidden() {}
+
+ override fun clearNotificationEffects() {}
+
+ override fun onNotificationClick(key: String, nv: NotificationVisibility) {}
+
+ override fun onNotificationActionClick(
+ key: String,
+ actionIndex: Int,
+ action: Notification.Action,
+ nv: NotificationVisibility,
+ generatedByAssistant: Boolean,
+ ) {}
+
+ override fun onNotificationError(
+ pkg: String,
+ tag: String,
+ id: Int,
+ uid: Int,
+ initialPid: Int,
+ message: String,
+ userId: Int,
+ ) {}
+
+ override fun onClearAllNotifications(userId: Int) {}
+
+ override fun onNotificationClear(
+ pkg: String,
+ userId: Int,
+ key: String,
+ dismissalSurface: Int,
+ dismissalSentiment: Int,
+ nv: NotificationVisibility,
+ ) {}
+
+ override fun onNotificationVisibilityChanged(
+ newlyVisibleKeys: Array<NotificationVisibility>,
+ noLongerVisibleKeys: Array<NotificationVisibility>,
+ ) {}
+
+ override fun onNotificationExpansionChanged(
+ key: String,
+ userAction: Boolean,
+ expanded: Boolean,
+ notificationLocation: Int,
+ ) {}
+
+ override fun onNotificationDirectReplied(key: String) {}
+
+ override fun onNotificationSmartSuggestionsAdded(
+ key: String,
+ smartReplyCount: Int,
+ smartActionCount: Int,
+ generatedByAssistant: Boolean,
+ editBeforeSending: Boolean,
+ ) {}
+
+ override fun onNotificationSmartReplySent(
+ key: String,
+ replyIndex: Int,
+ reply: CharSequence,
+ notificationLocation: Int,
+ modifiedBeforeSending: Boolean,
+ ) {}
+
+ override fun onNotificationSettingsViewed(key: String) {}
+
+ override fun onNotificationBubbleChanged(key: String, isBubble: Boolean, flags: Int) {}
+
+ override fun onBubbleMetadataFlagChanged(key: String, flags: Int) {}
+
+ override fun hideCurrentInputMethodForBubbles(displayId: Int) {}
+
+ override fun grantInlineReplyUriPermission(
+ key: String,
+ uri: Uri,
+ user: UserHandle,
+ packageName: String,
+ ) {}
+
+ override fun clearInlineReplyUriPermissions(key: String) {}
+
+ override fun onNotificationFeedbackReceived(key: String, feedback: Bundle) {}
+
+ override fun onGlobalActionsShown() {}
+
+ override fun onGlobalActionsHidden() {}
+
+ override fun shutdown() {}
+
+ override fun reboot(safeMode: Boolean) {}
+
+ override fun restart() {}
+
+ override fun addTile(tile: ComponentName) {}
+
+ override fun remTile(tile: ComponentName) {}
+
+ override fun clickTile(tile: ComponentName) {}
+
+ override fun handleSystemKey(key: KeyEvent) {}
+
+ override fun getLastSystemKey(): Int {
+ return -1
+ }
+
+ override fun showPinningEnterExitToast(entering: Boolean) {}
+
+ override fun showPinningEscapeToast() {}
+
+ override fun showAuthenticationDialog(
+ promptInfo: PromptInfo,
+ sysuiReceiver: IBiometricSysuiReceiver,
+ sensorIds: IntArray,
+ credentialAllowed: Boolean,
+ requireConfirmation: Boolean,
+ userId: Int,
+ operationId: Long,
+ opPackageName: String,
+ requestId: Long,
+ ) {}
+
+ override fun onBiometricAuthenticated(modality: Int) {}
+
+ override fun onBiometricHelp(modality: Int, message: String) {}
+
+ override fun onBiometricError(modality: Int, error: Int, vendorCode: Int) {}
+
+ override fun hideAuthenticationDialog(requestId: Long) {}
+
+ override fun setBiometicContextListener(listener: IBiometricContextListener) {}
+
+ override fun setUdfpsRefreshRateCallback(callback: IUdfpsRefreshRateRequestCallback) {}
+
+ override fun showInattentiveSleepWarning() {}
+
+ override fun dismissInattentiveSleepWarning(animated: Boolean) {}
+
+ override fun startTracing() {}
+
+ override fun stopTracing() {}
+
+ override fun isTracing(): Boolean {
+ return false
+ }
+
+ override fun suppressAmbientDisplay(suppress: Boolean) {}
+
+ override fun requestTileServiceListeningState(componentName: ComponentName, userId: Int) {}
+
+ override fun requestAddTile(
+ componentName: ComponentName,
+ label: CharSequence,
+ icon: Icon,
+ userId: Int,
+ callback: IAddTileResultCallback,
+ ) {}
+
+ override fun cancelRequestAddTile(packageName: String) {}
+
+ override fun setNavBarMode(navBarMode: Int) {}
+
+ override fun getNavBarMode(): Int {
+ return -1
+ }
+
+ override fun registerSessionListener(sessionFlags: Int, listener: ISessionListener) {}
+
+ override fun unregisterSessionListener(sessionFlags: Int, listener: ISessionListener) {}
+
+ override fun onSessionStarted(sessionType: Int, instanceId: InstanceId) {}
+
+ override fun onSessionEnded(sessionType: Int, instanceId: InstanceId) {}
+
+ override fun updateMediaTapToTransferSenderDisplay(
+ displayState: Int,
+ routeInfo: MediaRoute2Info,
+ undoCallback: IUndoMediaTransferCallback,
+ ) {}
+
+ override fun updateMediaTapToTransferReceiverDisplay(
+ displayState: Int,
+ routeInfo: MediaRoute2Info,
+ appIcon: Icon,
+ appName: CharSequence,
+ ) {}
+
+ override fun registerNearbyMediaDevicesProvider(provider: INearbyMediaDevicesProvider) {}
+
+ override fun unregisterNearbyMediaDevicesProvider(provider: INearbyMediaDevicesProvider) {}
+
+ override fun showRearDisplayDialog(currentBaseState: Int) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/StatusBarServiceKosmos.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/StatusBarServiceKosmos.kt
new file mode 100644
index 0000000..1304161
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/StatusBarServiceKosmos.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 android.internal.statusbar
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeStatusBarService by Kosmos.Fixture { FakeStatusBarService() }
+
+var Kosmos.statusBarService by Kosmos.Fixture { fakeStatusBarService }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/DemoModeKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/DemoModeKosmos.kt
new file mode 100644
index 0000000..39384fd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/DemoModeKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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
+
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mockDemoModeController by Kosmos.Fixture { mock<DemoModeController>() }
+
+var Kosmos.demoModeController by Kosmos.Fixture { mockDemoModeController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/InitControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/InitControllerKosmos.kt
new file mode 100644
index 0000000..13169e1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/InitControllerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.initController by Kosmos.Fixture { InitController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt
index 3a59f6a..601c145 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt
@@ -18,6 +18,8 @@
package com.android.systemui.keyguard.data.repository
import android.content.Context
+import androidx.collection.ArrayMap
+import com.android.internal.statusbar.StatusBarIcon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.settings.DisplayTracker
import com.android.systemui.statusbar.CommandQueue
@@ -31,6 +33,11 @@
CommandQueue(mock(Context::class.java), mock(DisplayTracker::class.java)) {
private val callbacks = mutableListOf<Callbacks>()
+ val icons = ArrayMap<String, StatusBarIcon>()
+
+ private val perDisplayDisableFlags1 = mutableMapOf<Int, Int>()
+ private val perDisplayDisableFlags2 = mutableMapOf<Int, Int>()
+
override fun addCallback(callback: Callbacks) {
callbacks.add(callback)
}
@@ -44,6 +51,23 @@
}
fun callbackCount(): Int = callbacks.size
+
+ override fun setIcon(slot: String, icon: StatusBarIcon) {
+ icons[slot] = icon
+ }
+
+ override fun disable(displayId: Int, state1: Int, state2: Int, animate: Boolean) {
+ perDisplayDisableFlags1[displayId] = state1
+ perDisplayDisableFlags2[displayId] = state2
+ }
+
+ override fun disable(displayId: Int, state1: Int, state2: Int) {
+ disable(displayId, state1, state2, /* animate= */ false)
+ }
+
+ fun disableFlags1ForDisplay(displayId: Int) = perDisplayDisableFlags1[displayId]
+
+ fun disableFlags2ForDisplay(displayId: Int) = perDisplayDisableFlags2[displayId]
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index a73c184..4d0e603 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -48,9 +48,8 @@
* with OFF -> GONE. Construct with initInLockscreen = false if your test requires this behavior.
*/
@SysUISingleton
-class FakeKeyguardTransitionRepository(
- private val initInLockscreen: Boolean = true,
-) : KeyguardTransitionRepository {
+class FakeKeyguardTransitionRepository(private val initInLockscreen: Boolean = true) :
+ KeyguardTransitionRepository {
private val _transitions =
MutableSharedFlow<TransitionStep>(replay = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val transitions: SharedFlow<TransitionStep> = _transitions
@@ -63,7 +62,7 @@
ownerName = "",
from = KeyguardState.OFF,
to = KeyguardState.LOCKSCREEN,
- animator = null
+ animator = null,
)
)
override var currentTransitionInfoInternal = _currentTransitionInfo.asStateFlow()
@@ -71,12 +70,7 @@
init {
// Seed with a FINISHED transition in OFF, same as the real repository.
_transitions.tryEmit(
- TransitionStep(
- KeyguardState.OFF,
- KeyguardState.OFF,
- 1f,
- TransitionState.FINISHED,
- )
+ TransitionStep(KeyguardState.OFF, KeyguardState.OFF, 1f, TransitionState.FINISHED)
)
if (initInLockscreen) {
@@ -173,7 +167,7 @@
transitionState = TransitionState.RUNNING,
from = from,
to = to,
- value = 0.5f
+ value = 0.5f,
)
)
testScheduler.runCurrent()
@@ -184,7 +178,7 @@
transitionState = TransitionState.RUNNING,
from = from,
to = to,
- value = 1f
+ value = 1f,
)
)
testScheduler.runCurrent()
@@ -208,7 +202,7 @@
this.sendTransitionStep(
step = step,
validateStep = validateStep,
- ownerName = step.ownerName
+ ownerName = step.ownerName,
)
}
@@ -240,9 +234,9 @@
to = to,
value = value,
transitionState = transitionState,
- ownerName = ownerName
+ ownerName = ownerName,
),
- validateStep: Boolean = true
+ validateStep: Boolean = true,
) {
if (step.transitionState == TransitionState.STARTED) {
_currentTransitionInfo.value =
@@ -273,7 +267,7 @@
fun sendTransitionStepJava(
coroutineScope: CoroutineScope,
step: TransitionStep,
- validateStep: Boolean = true
+ validateStep: Boolean = true,
): Job {
return coroutineScope.launch {
sendTransitionStep(step = step, validateStep = validateStep)
@@ -283,7 +277,7 @@
suspend fun sendTransitionSteps(
steps: List<TransitionStep>,
testScope: TestScope,
- validateSteps: Boolean = true
+ validateSteps: Boolean = true,
) {
steps.forEach {
sendTransitionStep(step = it, validateStep = validateSteps)
@@ -296,7 +290,7 @@
return if (info.animator == null) UUID.randomUUID() else null
}
- override suspend fun emitInitialStepsFromOff(to: KeyguardState) {
+ override suspend fun emitInitialStepsFromOff(to: KeyguardState, testSetup: Boolean) {
tryEmitInitialStepsFromOff(to)
}
@@ -318,14 +312,14 @@
1f,
TransitionState.FINISHED,
ownerName = "KeyguardTransitionRepository(boot)",
- ),
+ )
)
}
override suspend fun updateTransition(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
- state: TransitionState
+ state: TransitionState,
) = Unit
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index 38bc758..2f13ba4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -22,6 +22,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -38,5 +39,6 @@
alternateBouncerInteractor = alternateBouncerInteractor,
shadeInteractor = { shadeInteractor },
keyguardInteractor = { keyguardInteractor },
+ sceneInteractor = { sceneInteractor },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt
index 0c538ff..ab7ccb3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt
@@ -18,6 +18,7 @@
import android.os.fakeExecutorHandler
import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
val Kosmos.keyguardBlueprintViewModel by
@@ -25,5 +26,6 @@
KeyguardBlueprintViewModel(
fakeExecutorHandler,
keyguardBlueprintInteractor,
+ keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 38626a5..3c87106 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -47,6 +47,8 @@
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
alternateBouncerToLockscreenTransitionViewModel =
alternateBouncerToLockscreenTransitionViewModel,
+ alternateBouncerToOccludedTransitionViewModel =
+ alternateBouncerToOccludedTransitionViewModel,
aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
@@ -69,9 +71,12 @@
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
lockscreenToPrimaryBouncerTransitionViewModel =
lockscreenToPrimaryBouncerTransitionViewModel,
+ occludedToAlternateBouncerTransitionViewModel =
+ occludedToAlternateBouncerTransitionViewModel,
occludedToAodTransitionViewModel = occludedToAodTransitionViewModel,
occludedToDozingTransitionViewModel = occludedToDozingTransitionViewModel,
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+ offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel,
primaryBouncerToAodTransitionViewModel = primaryBouncerToAodTransitionViewModel,
primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..2acd1b4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModelKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.occludedToAlternateBouncerTransitionViewModel by Fixture {
+ OccludedToAlternateBouncerTransitionViewModel(animationFlow = keyguardTransitionAnimationFlow)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..5d62a0f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.offToLockscreenTransitionViewModel by Fixture {
+ OffToLockscreenTransitionViewModel(animationFlow = keyguardTransitionAnimationFlow)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationBarControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationBarControllerKosmos.kt
new file mode 100644
index 0000000..9e2039e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationBarControllerKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.mockNavigationBarController by Kosmos.Fixture { mock<NavigationBarController>() }
+
+var Kosmos.navigationBarController by Kosmos.Fixture { mockNavigationBarController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/PluginDependencyKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/PluginDependencyKosmos.kt
new file mode 100644
index 0000000..f1388e9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/PluginDependencyKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.plugins
+
+import android.testing.LeakCheck
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.utils.leaks.FakePluginManager
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.mock
+
+val Kosmos.leakCheck by Kosmos.Fixture { LeakCheck() }
+
+val Kosmos.fakePluginManager by Kosmos.Fixture { FakePluginManager(leakCheck) }
+
+var Kosmos.pluginManager by Kosmos.Fixture { fakePluginManager }
+
+val Kosmos.pluginDependencyProvider by Kosmos.Fixture { PluginDependencyProvider { pluginManager } }
+
+val Kosmos.mockPluginDependencyProvider by Kosmos.Fixture { mock<PluginDependencyProvider>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index d37d8f3..dbb3e38 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -19,14 +19,15 @@
import android.content.res.mainResources
import androidx.lifecycle.LifecycleCoroutineScope
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.footerActionsController
import com.android.systemui.qs.footerActionsViewModelFactory
+import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModel
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.shade.transition.largeScreenShadeInterpolator
import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
-import com.android.systemui.statusbar.phone.keyguardBypassController
import com.android.systemui.statusbar.sysuiStatusBarStateController
val Kosmos.qsFragmentComposeViewModelFactory by
@@ -41,11 +42,12 @@
footerActionsViewModelFactory,
footerActionsController,
sysuiStatusBarStateController,
- keyguardBypassController,
+ deviceEntryInteractor,
disableFlagsRepository,
largeScreenShadeInterpolator,
configurationInteractor,
largeScreenHeaderHelper,
+ tileSquishinessInteractor,
lifecycleScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepositoryKosmos.kt
new file mode 100644
index 0000000..d9fad32
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.tileSquishinessRepository by Kosmos.Fixture { TileSquishinessRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index 3f62b4d..546129f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -20,6 +20,9 @@
import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.tileSquishinessViewModel
val Kosmos.infiniteGridLayout by
- Kosmos.Fixture { InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel) }
+ Kosmos.Fixture {
+ InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel, tileSquishinessViewModel)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractorKosmos.kt
new file mode 100644
index 0000000..23db70f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractorKosmos.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.qs.panels.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.data.repository.tileSquishinessRepository
+
+val Kosmos.tileSquishinessInteractor by
+ Kosmos.Fixture { TileSquishinessInteractor(tileSquishinessRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
index 40d2624..babbd50 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
@@ -27,6 +27,7 @@
currentTilesInteractor,
fixedColumnsSizeViewModel,
quickQuickSettingsRowInteractor,
+ tileSquishinessViewModel,
iconTilesViewModel,
applicationCoroutineScope,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModelKosmos.kt
new file mode 100644
index 0000000..ecc8cd1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModelKosmos.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.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
+
+val Kosmos.tileSquishinessViewModel by
+ Kosmos.Fixture { TileSquishinessViewModel(tileSquishinessInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
index 1ceab68..a9f9c82 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
@@ -20,3 +20,13 @@
import com.android.systemui.util.mockito.mock
var Kosmos.shadeViewController by Kosmos.Fixture { mock<ShadeViewController>() }
+
+val Kosmos.mockNotificationShadeWindowViewController by
+ Kosmos.Fixture { mock<NotificationShadeWindowViewController>() }
+
+var Kosmos.notificationShadeWindowViewController by
+ Kosmos.Fixture { mockNotificationShadeWindowViewController }
+
+val Kosmos.mockShadeSurface by Kosmos.Fixture { mock<ShadeSurface>() }
+
+var Kosmos.shadeSurface by Kosmos.Fixture { mockShadeSurface }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
index 27f7f68..f571c1b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
@@ -19,4 +19,10 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.util.mockito.mock
-var Kosmos.commandQueue by Kosmos.Fixture { mock<CommandQueue>() }
+val Kosmos.mockCommandQueue by Kosmos.Fixture { mock<CommandQueue>() }
+
+var Kosmos.commandQueue by Kosmos.Fixture { mockCommandQueue }
+
+val Kosmos.mockCommandQueueCallbacks by Kosmos.Fixture { mock<CommandQueue.Callbacks>() }
+
+var Kosmos.commandQueueCallbacks by Kosmos.Fixture { mockCommandQueue }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
index 554bdbe..d436cd4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
@@ -19,5 +19,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.util.mockito.mock
-var Kosmos.notificationRemoteInputManager by
+val Kosmos.mockNotificationRemoteInputManager by
Kosmos.Fixture { mock<NotificationRemoteInputManager>() }
+
+var Kosmos.notificationRemoteInputManager by Kosmos.Fixture { mockNotificationRemoteInputManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/CommandQueueInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/CommandQueueInitializerKosmos.kt
new file mode 100644
index 0000000..cba4e8e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/CommandQueueInitializerKosmos.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.systemui.statusbar.core
+
+import android.content.testableContext
+import android.internal.statusbar.fakeStatusBarService
+import com.android.systemui.initController
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.navigationbar.mockNavigationBarController
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.mockCommandQueueCallbacks
+
+var Kosmos.commandQueueInitializer by
+ Kosmos.Fixture {
+ CommandQueueInitializer(
+ testableContext,
+ fakeCommandQueue,
+ { mockCommandQueueCallbacks },
+ fakeStatusBarModeRepository,
+ initController,
+ fakeStatusBarService,
+ mockNavigationBarController,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt
new file mode 100644
index 0000000..edd6604
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.core
+
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener
+import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
+
+class FakeStatusBarInitializer(
+ private val statusBarViewController: PhoneStatusBarViewController,
+ private val statusBarTransitions: PhoneStatusBarTransitions,
+) : StatusBarInitializer {
+
+ override var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
+ set(value) {
+ field = value
+ value?.onStatusBarViewUpdated(statusBarViewController, statusBarTransitions)
+ }
+
+ override fun initializeStatusBar() {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
new file mode 100644
index 0000000..d103200
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.core
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.phoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.phoneStatusBarViewController
+
+val Kosmos.fakeStatusBarInitializer by
+ Kosmos.Fixture {
+ FakeStatusBarInitializer(phoneStatusBarViewController, phoneStatusBarTransitions)
+ }
+
+var Kosmos.statusBarInitializer by Kosmos.Fixture { fakeStatusBarInitializer }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
new file mode 100644
index 0000000..c53e44d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.core
+
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.mockDemoModeController
+import com.android.systemui.plugins.mockPluginDependencyProvider
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.shade.mockNotificationShadeWindowViewController
+import com.android.systemui.shade.mockShadeSurface
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.mockNotificationRemoteInputManager
+import com.android.systemui.statusbar.phone.mockAutoHideController
+import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore
+import com.android.systemui.statusbar.window.fakeStatusBarWindowController
+import com.android.wm.shell.bubbles.bubblesOptional
+
+val Kosmos.statusBarOrchestrator by
+ Kosmos.Fixture {
+ StatusBarOrchestrator(
+ applicationCoroutineScope,
+ fakeStatusBarInitializer,
+ fakeStatusBarWindowController,
+ fakeStatusBarModeRepository,
+ mockDemoModeController,
+ mockPluginDependencyProvider,
+ mockAutoHideController,
+ mockNotificationRemoteInputManager,
+ { mockNotificationShadeWindowViewController },
+ mockShadeSurface,
+ bubblesOptional,
+ statusBarWindowStateRepositoryStore,
+ powerInteractor,
+ primaryBouncerInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index a9e117a..237f7e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -42,6 +42,7 @@
import com.android.systemui.keyguard.ui.viewmodel.occludedToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.occludedToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.occludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.offToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.primaryBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.primaryBouncerToLockscreenTransitionViewModel
import com.android.systemui.kosmos.Kosmos
@@ -85,6 +86,7 @@
occludedToAodTransitionViewModel = occludedToAodTransitionViewModel,
occludedToGoneTransitionViewModel = occludedToGoneTransitionViewModel,
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+ offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel,
primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel =
primaryBouncerToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt
new file mode 100644
index 0000000..090ce31
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mockAutoHideController by Kosmos.Fixture { mock<AutoHideController>() }
+
+var Kosmos.autoHideController by Kosmos.Fixture { mockAutoHideController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/PhoneStatusBarKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/PhoneStatusBarKosmos.kt
new file mode 100644
index 0000000..603ee08
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/PhoneStatusBarKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.Mockito.mock
+
+val Kosmos.mockPhoneStatusBarViewController: PhoneStatusBarViewController by
+ Kosmos.Fixture { mock(PhoneStatusBarViewController::class.java) }
+
+var Kosmos.phoneStatusBarViewController by Kosmos.Fixture { mockPhoneStatusBarViewController }
+
+val Kosmos.mockPhoneStatusBarTransitions: PhoneStatusBarTransitions by
+ Kosmos.Fixture { mock(PhoneStatusBarTransitions::class.java) }
+
+var Kosmos.phoneStatusBarTransitions by Kosmos.Fixture { mockPhoneStatusBarTransitions }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
new file mode 100644
index 0000000..528c9d9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.window
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.fragments.FragmentHostManager
+import java.util.Optional
+
+class FakeStatusBarWindowController : StatusBarWindowController {
+
+ var isAttached = false
+ private set
+
+ override val statusBarHeight: Int = 0
+
+ override fun refreshStatusBarHeight() {}
+
+ override fun attach() {
+ isAttached = true
+ }
+
+ override fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams) {}
+
+ override val backgroundView: View
+ get() = throw NotImplementedError()
+
+ override val fragmentHostManager: FragmentHostManager
+ get() = throw NotImplementedError()
+
+ override fun wrapAnimationControllerIfInStatusBar(
+ rootView: View,
+ animationController: ActivityTransitionAnimator.Controller,
+ ): Optional<ActivityTransitionAnimator.Controller> = Optional.empty()
+
+ override fun setForceStatusBarVisible(forceStatusBarVisible: Boolean) {}
+
+ override fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
new file mode 100644
index 0000000..c198b35
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.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.statusbar.window
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeStatusBarWindowController by Kosmos.Fixture { FakeStatusBarWindowController() }
+
+var Kosmos.statusBarWindowController by Kosmos.Fixture { fakeStatusBarWindowController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/FakeStatusBarWindowStatePerDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/FakeStatusBarWindowStatePerDisplayRepository.kt
new file mode 100644
index 0000000..6532a7e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/FakeStatusBarWindowStatePerDisplayRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.window.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.window.data.model.StatusBarWindowState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeStatusBarWindowStateRepositoryStore : StatusBarWindowStateRepositoryStore {
+
+ private val perDisplayRepos = mutableMapOf<Int, FakeStatusBarWindowStatePerDisplayRepository>()
+
+ override val defaultDisplay: FakeStatusBarWindowStatePerDisplayRepository =
+ forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): FakeStatusBarWindowStatePerDisplayRepository =
+ perDisplayRepos.computeIfAbsent(displayId) {
+ FakeStatusBarWindowStatePerDisplayRepository()
+ }
+}
+
+class FakeStatusBarWindowStatePerDisplayRepository : StatusBarWindowStatePerDisplayRepository {
+
+ private val _windowState = MutableStateFlow(StatusBarWindowState.Hidden)
+
+ override val windowState = _windowState.asStateFlow()
+
+ fun setWindowState(state: StatusBarWindowState) {
+ _windowState.value = state
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt
index e2b7f5f..2205a3b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt
@@ -21,6 +21,9 @@
import com.android.systemui.settings.displayTracker
import com.android.systemui.statusbar.commandQueue
+val Kosmos.fakeStatusBarWindowStateRepositoryStore by
+ Kosmos.Fixture { FakeStatusBarWindowStateRepositoryStore() }
+
class KosmosStatusBarWindowStatePerDisplayRepositoryFactory(private val kosmos: Kosmos) :
StatusBarWindowStatePerDisplayRepositoryFactory {
override fun create(displayId: Int): StatusBarWindowStatePerDisplayRepositoryImpl {
@@ -32,7 +35,7 @@
}
}
-val Kosmos.statusBarWindowStateRepositoryStore by
+var Kosmos.statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore by
Kosmos.Fixture {
StatusBarWindowStateRepositoryStoreImpl(
displayId = displayTracker.defaultDisplayId,
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
index 428eb57..b4b8715 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -84,10 +84,10 @@
try {
mOutputWriter = new PrintWriter(mOutputFile);
} catch (IOException e) {
- throw new RuntimeException("Failed to crete logfile. File=" + mOutputFile, e);
+ throw new RuntimeException("Failed to create logfile. File=" + mOutputFile, e);
}
- // Crete the "latest" symlink.
+ // Create the "latest" symlink.
Path symlink = Paths.get(tmpdir, basename + "latest.csv");
try {
if (Files.exists(symlink)) {
@@ -96,7 +96,7 @@
Files.createSymbolicLink(symlink, Paths.get(mOutputFile.getName()));
} catch (IOException e) {
- throw new RuntimeException("Failed to crete logfile. File=" + mOutputFile, e);
+ throw new RuntimeException("Failed to create logfile. File=" + mOutputFile, e);
}
Log.i(TAG, "Test result stats file: " + mOutputFile);
diff --git a/ravenwood/runtime-jni/ravenwood_sysprop.cpp b/ravenwood/runtime-jni/ravenwood_sysprop.cpp
index 4fb61b6..aafc426 100644
--- a/ravenwood/runtime-jni/ravenwood_sysprop.cpp
+++ b/ravenwood/runtime-jni/ravenwood_sysprop.cpp
@@ -56,7 +56,7 @@
if (key == nullptr || *key == '\0') return false;
if (value == nullptr) value = "";
bool read_only = !strncmp(key, "ro.", 3);
- if (!read_only && strlen(value) >= PROP_VALUE_MAX) return -1;
+ if (!read_only && strlen(value) >= PROP_VALUE_MAX) return false;
std::lock_guard lock(g_properties_lock);
auto [it, success] = g_properties.emplace(key, value);
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 082459b..b6c8fc7 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -16,22 +16,35 @@
package com.android.server.appfunctions;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DISABLED;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED;
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB;
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE;
+
import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
-import android.app.appfunctions.AppFunctionService;
+import android.app.appfunctions.AppFunctionManager;
+import android.app.appfunctions.AppFunctionManagerHelper;
+import android.app.appfunctions.AppFunctionRuntimeMetadata;
import android.app.appfunctions.AppFunctionStaticMetadataHelper;
import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.IAppFunctionEnabledCallback;
import android.app.appfunctions.IAppFunctionManager;
import android.app.appfunctions.IAppFunctionService;
import android.app.appfunctions.ICancellationCallback;
import android.app.appfunctions.IExecuteAppFunctionCallback;
import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
+import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchManager.SearchContext;
import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetByDocumentIdRequest;
+import android.app.appsearch.PutDocumentsRequest;
import android.app.appsearch.observer.DocumentChangeInfo;
import android.app.appsearch.observer.ObserverCallback;
import android.app.appsearch.observer.ObserverSpec;
@@ -40,12 +53,15 @@
import android.content.Intent;
import android.os.Binder;
import android.os.CancellationSignal;
-import android.os.IBinder;
import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.ParcelableException;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
import com.android.server.SystemService.TargetUser;
@@ -54,6 +70,7 @@
import java.util.Objects;
import java.util.concurrent.CompletionException;
+import java.util.concurrent.Executor;
/** Implementation of the AppFunctionManagerService. */
public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
@@ -64,6 +81,7 @@
private final ServiceHelper mInternalServiceHelper;
private final ServiceConfig mServiceConfig;
private final Context mContext;
+ private final Object mLock = new Object();
public AppFunctionManagerServiceImpl(@NonNull Context context) {
this(
@@ -180,52 +198,214 @@
return;
}
- var unused =
- mCallerValidator
- .verifyCallerCanExecuteAppFunction(
- callingUid,
- callingPid,
- requestInternal.getCallingPackage(),
- targetPackageName,
- requestInternal.getClientRequest().getFunctionIdentifier())
- .thenAccept(
- canExecute -> {
- if (!canExecute) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_DENIED,
- "Caller does not have permission to execute"
- + " the appfunction",
- /* extras= */ null));
- return;
- }
- Intent serviceIntent =
- mInternalServiceHelper.resolveAppFunctionService(
- targetPackageName, targetUser);
- if (serviceIntent == null) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse
- .RESULT_INTERNAL_ERROR,
- "Cannot find the target service.",
- /* extras= */ null));
- return;
- }
- bindAppFunctionServiceUnchecked(
- requestInternal,
- serviceIntent,
- targetUser,
- localCancelTransport,
- safeExecuteAppFunctionCallback,
- /* bindFlags= */ Context.BIND_AUTO_CREATE
- | Context.BIND_FOREGROUND_SERVICE);
- })
- .exceptionally(
- ex -> {
- safeExecuteAppFunctionCallback.onResult(
- mapExceptionToExecuteAppFunctionResponse(ex));
- return null;
- });
+ mCallerValidator
+ .verifyCallerCanExecuteAppFunction(
+ callingUid,
+ callingPid,
+ requestInternal.getCallingPackage(),
+ targetPackageName,
+ requestInternal.getClientRequest().getFunctionIdentifier())
+ .thenAccept(
+ canExecute -> {
+ if (!canExecute) {
+ safeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_DENIED,
+ "Caller does not have permission to execute the"
+ + " appfunction",
+ /* extras= */ null));
+ }
+ })
+ .thenCompose(
+ isEnabled ->
+ isAppFunctionEnabled(
+ requestInternal.getClientRequest().getFunctionIdentifier(),
+ requestInternal.getClientRequest().getTargetPackageName(),
+ getAppSearchManagerAsUser(requestInternal.getUserHandle()),
+ THREAD_POOL_EXECUTOR))
+ .thenAccept(
+ isEnabled -> {
+ if (!isEnabled) {
+ throw new DisabledAppFunctionException(
+ "The app function is disabled");
+ }
+ })
+ .thenAccept(
+ unused -> {
+ Intent serviceIntent =
+ mInternalServiceHelper.resolveAppFunctionService(
+ targetPackageName, targetUser);
+ if (serviceIntent == null) {
+ safeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+ "Cannot find the target service.",
+ /* extras= */ null));
+ return;
+ }
+ bindAppFunctionServiceUnchecked(
+ requestInternal,
+ serviceIntent,
+ targetUser,
+ localCancelTransport,
+ safeExecuteAppFunctionCallback,
+ /* bindFlags= */ Context.BIND_AUTO_CREATE
+ | Context.BIND_FOREGROUND_SERVICE);
+ })
+ .exceptionally(
+ ex -> {
+ safeExecuteAppFunctionCallback.onResult(
+ mapExceptionToExecuteAppFunctionResponse(ex));
+ return null;
+ });
+ }
+
+ private static AndroidFuture<Boolean> isAppFunctionEnabled(
+ @NonNull String functionIdentifier,
+ @NonNull String targetPackage,
+ @NonNull AppSearchManager appSearchManager,
+ @NonNull Executor executor) {
+ AndroidFuture<Boolean> future = new AndroidFuture<>();
+ AppFunctionManagerHelper.isAppFunctionEnabled(
+ functionIdentifier,
+ targetPackage,
+ appSearchManager,
+ executor,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(@NonNull Boolean result) {
+ future.complete(result);
+ }
+
+ @Override
+ public void onError(@NonNull Exception error) {
+ future.completeExceptionally(error);
+ }
+ });
+ return future;
+ }
+
+ @Override
+ public void setAppFunctionEnabled(
+ @NonNull String callingPackage,
+ @NonNull String functionIdentifier,
+ @NonNull UserHandle userHandle,
+ @AppFunctionManager.EnabledState int enabledState,
+ @NonNull IAppFunctionEnabledCallback callback) {
+ try {
+ mCallerValidator.validateCallingPackage(callingPackage);
+ } catch (SecurityException e) {
+ reportException(callback, e);
+ return;
+ }
+ THREAD_POOL_EXECUTOR.execute(
+ () -> {
+ try {
+ // TODO(357551503): Instead of holding a global lock, hold a per-package
+ // lock.
+ synchronized (mLock) {
+ setAppFunctionEnabledInternalLocked(
+ callingPackage, functionIdentifier, userHandle, enabledState);
+ }
+ callback.onSuccess();
+ } catch (Exception e) {
+ Slog.e(TAG, "Error in setAppFunctionEnabled: ", e);
+ reportException(callback, e);
+ }
+ });
+ }
+
+ private static void reportException(
+ @NonNull IAppFunctionEnabledCallback callback, @NonNull Exception exception) {
+ try {
+ callback.onError(new ParcelableException(exception));
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to report the exception", e);
+ }
+ }
+
+ /**
+ * Sets the enabled status of a specified app function.
+ * <p>
+ * Required to hold a lock to call this function to avoid document changes during the process.
+ */
+ @WorkerThread
+ @GuardedBy("mLock")
+ private void setAppFunctionEnabledInternalLocked(
+ @NonNull String callingPackage,
+ @NonNull String functionIdentifier,
+ @NonNull UserHandle userHandle,
+ @AppFunctionManager.EnabledState int enabledState)
+ throws Exception {
+ AppSearchManager perUserAppSearchManager = getAppSearchManagerAsUser(userHandle);
+
+ if (perUserAppSearchManager == null) {
+ throw new IllegalStateException(
+ "AppSearchManager not found for user:" + userHandle.getIdentifier());
+ }
+ SearchContext runtimeMetadataSearchContext =
+ new SearchContext.Builder(APP_FUNCTION_RUNTIME_METADATA_DB).build();
+
+ try (FutureAppSearchSession runtimeMetadataSearchSession =
+ new FutureAppSearchSessionImpl(
+ perUserAppSearchManager,
+ THREAD_POOL_EXECUTOR,
+ runtimeMetadataSearchContext)) {
+ AppFunctionRuntimeMetadata existingMetadata =
+ new AppFunctionRuntimeMetadata(
+ getRuntimeMetadataGenericDocument(
+ callingPackage,
+ functionIdentifier,
+ runtimeMetadataSearchSession));
+ AppFunctionRuntimeMetadata.Builder newMetadata =
+ new AppFunctionRuntimeMetadata.Builder(existingMetadata);
+ switch (enabledState) {
+ case AppFunctionManager.APP_FUNCTION_STATE_DEFAULT -> {
+ newMetadata.setEnabled(null);
+ }
+ case APP_FUNCTION_STATE_ENABLED -> {
+ newMetadata.setEnabled(true);
+ }
+ case APP_FUNCTION_STATE_DISABLED -> {
+ newMetadata.setEnabled(false);
+ }
+ default ->
+ throw new IllegalArgumentException(
+ "Value of EnabledState is unsupported.");
+ }
+ AppSearchBatchResult<String, Void> putDocumentBatchResult =
+ runtimeMetadataSearchSession
+ .put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(newMetadata.build())
+ .build())
+ .get();
+ if (!putDocumentBatchResult.isSuccess()) {
+ throw new IllegalStateException("Failed writing updated doc to AppSearch due to "
+ + putDocumentBatchResult);
+ }
+ }
+ }
+
+ @WorkerThread
+ @NonNull
+ private AppFunctionRuntimeMetadata getRuntimeMetadataGenericDocument(
+ @NonNull String packageName,
+ @NonNull String functionId,
+ @NonNull FutureAppSearchSession runtimeMetadataSearchSession)
+ throws Exception {
+ String documentId =
+ AppFunctionRuntimeMetadata.getDocumentIdForAppFunction(packageName, functionId);
+ GetByDocumentIdRequest request =
+ new GetByDocumentIdRequest.Builder(APP_FUNCTION_RUNTIME_NAMESPACE)
+ .addIds(documentId)
+ .build();
+ AppSearchBatchResult<String, GenericDocument> result =
+ runtimeMetadataSearchSession.getByDocumentId(request).get();
+ if (result.isSuccess()) {
+ return new AppFunctionRuntimeMetadata((result.getSuccesses().get(documentId)));
+ }
+ throw new IllegalArgumentException("Function " + functionId + " does not exist");
}
private void bindAppFunctionServiceUnchecked(
@@ -302,24 +482,27 @@
}
}
+ private AppSearchManager getAppSearchManagerAsUser(@NonNull UserHandle userHandle) {
+ return mContext.createContextAsUser(userHandle, /* flags= */ 0)
+ .getSystemService(AppSearchManager.class);
+ }
+
private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) {
if (e instanceof CompletionException) {
e = e.getCause();
}
-
- if (e instanceof AppSearchException) {
- AppSearchException appSearchException = (AppSearchException) e;
- return ExecuteAppFunctionResponse.newFailure(
+ int resultCode = ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR;
+ if (e instanceof AppSearchException appSearchException) {
+ resultCode =
mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(
- appSearchException.getResultCode()),
- appSearchException.getMessage(),
- /* extras= */ null);
+ appSearchException.getResultCode());
+ } else if (e instanceof SecurityException) {
+ resultCode = ExecuteAppFunctionResponse.RESULT_DENIED;
+ } else if (e instanceof DisabledAppFunctionException) {
+ resultCode = ExecuteAppFunctionResponse.RESULT_DISABLED;
}
-
return ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
- e.getMessage(),
- /* extras= */ null);
+ resultCode, e.getMessage(), /* extras= */ null);
}
private int mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(int resultCode) {
@@ -434,4 +617,11 @@
}
}
}
+
+ /** Throws when executing a disabled app function. */
+ private static class DisabledAppFunctionException extends RuntimeException {
+ private DisabledAppFunctionException(@NonNull String errorMessage) {
+ super(errorMessage);
+ }
+ }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java
index 070a99d..ffca849 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java
@@ -65,8 +65,7 @@
@NonNull UserHandle userHandle,
@NonNull RunServiceCallCallback<T> callback) {
OneOffServiceConnection serviceConnection =
- new OneOffServiceConnection(
- intent, bindFlags, userHandle, callback);
+ new OneOffServiceConnection(intent, bindFlags, userHandle, callback);
return serviceConnection.bindAndRun();
}
@@ -93,7 +92,7 @@
boolean bindServiceResult =
mContext.bindServiceAsUser(mIntent, this, mFlags, mUserHandle);
- if(!bindServiceResult) {
+ if (!bindServiceResult) {
safeUnbind();
}
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 5b271a3..7474df2 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -87,7 +87,7 @@
# replaces 27510 with a row per notification
27531 notification_visibility (key|3),(visibile|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1)
# a notification emited noise, vibration, or light
-27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1),(politeness|1)
+27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1),(politeness|1),(mute_reason|1)
# a notification was added to a autogroup
27533 notification_autogrouped (key|3)
# notification was removed from an autogroup
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index fbe593f..682eb76 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -25,6 +25,7 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -91,6 +92,7 @@
* Monitors the health of packages on the system and notifies interested observers when packages
* fail. On failure, the registered observer with the least user impacting mitigation will
* be notified.
+ * @hide
*/
public class PackageWatchdog {
private static final String TAG = "PackageWatchdog";
@@ -108,13 +110,25 @@
private static final long NUMBER_OF_NATIVE_CRASH_POLLS = 10;
+ /** Reason for package failure could not be determined. */
public static final int FAILURE_REASON_UNKNOWN = 0;
+
+ /** The package had a native crash. */
public static final int FAILURE_REASON_NATIVE_CRASH = 1;
+
+ /** The package failed an explicit health check. */
public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2;
+
+ /** The app crashed. */
public static final int FAILURE_REASON_APP_CRASH = 3;
+
+ /** The app was not responding. */
public static final int FAILURE_REASON_APP_NOT_RESPONDING = 4;
+
+ /** The device was boot looping. */
public static final int FAILURE_REASON_BOOT_LOOP = 5;
+ /** @hide */
@IntDef(prefix = { "FAILURE_REASON_" }, value = {
FAILURE_REASON_UNKNOWN,
FAILURE_REASON_NATIVE_CRASH,
@@ -186,7 +200,8 @@
// aborted.
private static final String METADATA_FILE = "/metadata/watchdog/mitigation_count.txt";
- @GuardedBy("PackageWatchdog.class")
+ private static final Object sPackageWatchdogLock = new Object();
+ @GuardedBy("sPackageWatchdogLock")
private static PackageWatchdog sPackageWatchdog;
private final Object mLock = new Object();
@@ -278,8 +293,8 @@
}
/** Creates or gets singleton instance of PackageWatchdog. */
- public static PackageWatchdog getInstance(Context context) {
- synchronized (PackageWatchdog.class) {
+ public static @NonNull PackageWatchdog getInstance(@NonNull Context context) {
+ synchronized (sPackageWatchdogLock) {
if (sPackageWatchdog == null) {
new PackageWatchdog(context);
}
@@ -290,6 +305,7 @@
/**
* Called during boot to notify when packages are ready on the device so we can start
* binding.
+ * @hide
*/
public void onPackagesReady() {
synchronized (mLock) {
@@ -311,6 +327,7 @@
*
* <p>Observers are expected to call this on boot. It does not specify any packages but
* it will resume observing any packages requested from a previous boot.
+ * @hide
*/
public void registerHealthObserver(PackageHealthObserver observer) {
synchronized (mLock) {
@@ -344,6 +361,7 @@
*
* <p>If {@code durationMs} is less than 1, a default monitoring duration
* {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
+ * @hide
*/
public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
long durationMs) {
@@ -407,6 +425,7 @@
* Unregisters {@code observer} from listening to package failure.
* Additionally, this stops observing any packages that may have previously been observed
* even from a previous boot.
+ * @hide
*/
public void unregisterHealthObserver(PackageHealthObserver observer) {
mLongTaskHandler.post(() -> {
@@ -425,7 +444,7 @@
*
* <p>This method could be called frequently if there is a severe problem on the device.
*/
- public void onPackageFailure(List<VersionedPackage> packages,
+ public void onPackageFailure(@NonNull List<VersionedPackage> packages,
@FailureReasons int failureReason) {
if (packages == null) {
Slog.w(TAG, "Could not resolve a list of failing packages");
@@ -566,6 +585,7 @@
*
* Note: PackageWatchdog considers system_server restart loop as bootloop. Full reboots
* are not counted in bootloop.
+ * @hide
*/
@SuppressWarnings("GuardedBy")
public void noteBoot() {
@@ -620,7 +640,7 @@
// TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
// avoid holding lock?
// This currently adds about 7ms extra to shutdown thread
- /** Writes the package information to file during shutdown. */
+ /** @hide Writes the package information to file during shutdown. */
public void writeNow() {
synchronized (mLock) {
// Must only run synchronous tasks as this runs on the ShutdownThread and no other
@@ -674,6 +694,7 @@
* Since this method can eventually trigger a rollback, it should be called
* only once boot has completed {@code onBootCompleted} and not earlier, because the install
* session must be entirely completed before we try to rollback.
+ * @hide
*/
public void scheduleCheckAndMitigateNativeCrashes() {
Slog.i(TAG, "Scheduling " + mNumberOfNativeCrashPollsRemaining + " polls to check "
@@ -695,7 +716,9 @@
return mPackagesExemptFromImpactLevelThreshold;
}
- /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
+ /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}.
+ * @hide
+ */
@Retention(SOURCE)
@IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
@@ -787,7 +810,7 @@
* Identifier for the observer, should not change across device updates otherwise the
* watchdog may drop observing packages with the old name.
*/
- String getUniqueIdentifier();
+ @NonNull String getUniqueIdentifier();
/**
* An observer will not be pruned if this is set, even if the observer is not explicitly
@@ -804,7 +827,7 @@
* <p> A persistent observer may choose to start observing certain failing packages, even if
* it has not explicitly asked to watch the package with {@link #startObservingHealth}.
*/
- default boolean mayObservePackage(String packageName) {
+ default boolean mayObservePackage(@NonNull String packageName) {
return false;
}
}
@@ -1240,7 +1263,7 @@
}
}
- /** Convert a {@code LongArrayQueue} to a String of comma-separated values. */
+ /** @hide Convert a {@code LongArrayQueue} to a String of comma-separated values. */
public static String longArrayQueueToString(LongArrayQueue queue) {
if (queue.size() > 0) {
StringBuilder sb = new StringBuilder();
@@ -1254,7 +1277,7 @@
return "";
}
- /** Parse a comma-separated String of longs into a LongArrayQueue. */
+ /** @hide Parse a comma-separated String of longs into a LongArrayQueue. */
public static LongArrayQueue parseLongArrayQueue(String commaSeparatedValues) {
LongArrayQueue result = new LongArrayQueue();
if (!TextUtils.isEmpty(commaSeparatedValues)) {
@@ -1268,7 +1291,7 @@
/** Dump status of every observer in mAllObservers. */
- public void dump(PrintWriter pw) {
+ public void dump(@NonNull PrintWriter pw) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.println("Package Watchdog status");
ipw.increaseIndent();
@@ -1395,6 +1418,7 @@
/**
* Increments failure counts of {@code packageName}.
* @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
+ * @hide
*/
@GuardedBy("mLock")
public boolean onPackageFailureLocked(String packageName) {
@@ -1514,6 +1538,7 @@
}
}
+ /** @hide */
@Retention(SOURCE)
@IntDef(value = {
HealthCheckState.ACTIVE,
@@ -1603,7 +1628,9 @@
updateHealthCheckStateLocked();
}
- /** Writes the salient fields to disk using {@code out}. */
+ /** Writes the salient fields to disk using {@code out}.
+ * @hide
+ */
@GuardedBy("mLock")
public void writeLocked(TypedXmlSerializer out) throws IOException {
out.startTag(null, TAG_PACKAGE);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index a13ce65..bae9a67 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -40,6 +40,7 @@
import android.aconfigd.Aconfigd.StorageReturnMessage;
import android.aconfigd.Aconfigd.StorageReturnMessages;
import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
+import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -491,14 +492,18 @@
static void writeFlagOverrideRequest(
ProtoOutputStream proto, String packageName, String flagName, String flagValue,
boolean isLocal) {
+ int localOverrideTag = supportImmediateLocalOverrides()
+ ? StorageRequestMessage.LOCAL_IMMEDIATE
+ : StorageRequestMessage.LOCAL_ON_REBOOT;
+
long msgsToken = proto.start(StorageRequestMessages.MSGS);
long msgToken = proto.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE);
proto.write(StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, packageName);
proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flagName);
proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flagValue);
proto.write(StorageRequestMessage.FlagOverrideMessage.OVERRIDE_TYPE, isLocal
- ? StorageRequestMessage.LOCAL_ON_REBOOT
- : StorageRequestMessage.SERVER_ON_REBOOT);
+ ? localOverrideTag
+ : StorageRequestMessage.SERVER_ON_REBOOT);
proto.end(msgToken);
proto.end(msgsToken);
}
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 9b51b6a..4f6da3b 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -205,4 +205,12 @@
metadata {
purpose: PURPOSE_BUGFIX
}
-}
\ No newline at end of file
+}
+
+flag {
+ name: "defer_display_events_when_frozen"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "Defer submitting display events to frozen processes."
+ bug: "326315985"
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index feef540..4c91789 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -725,7 +725,7 @@
return -1;
}
- if (!Utils.isValidAuthenticatorConfig(promptInfo)) {
+ if (!Utils.isValidAuthenticatorConfig(getContext(), promptInfo)) {
throw new SecurityException("Invalid authenticator configuration");
}
@@ -763,7 +763,7 @@
+ ", Caller=" + callingUserId
+ ", Authenticators=" + authenticators);
- if (!Utils.isValidAuthenticatorConfig(authenticators)) {
+ if (!Utils.isValidAuthenticatorConfig(getContext(), authenticators)) {
throw new SecurityException("Invalid authenticator configuration");
}
@@ -1038,7 +1038,7 @@
+ ", Caller=" + callingUserId
+ ", Authenticators=" + authenticators);
- if (!Utils.isValidAuthenticatorConfig(authenticators)) {
+ if (!Utils.isValidAuthenticatorConfig(getContext(), authenticators)) {
throw new SecurityException("Invalid authenticator configuration");
}
@@ -1060,7 +1060,7 @@
Slog.d(TAG, "getSupportedModalities: Authenticators=" + authenticators);
- if (!Utils.isValidAuthenticatorConfig(authenticators)) {
+ if (!Utils.isValidAuthenticatorConfig(getContext(), authenticators)) {
throw new SecurityException("Invalid authenticator configuration");
}
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 407ef1e..de7bce7 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
import static android.hardware.biometrics.BiometricManager.Authenticators;
@@ -233,17 +234,18 @@
* @param promptInfo
* @return
*/
- static boolean isValidAuthenticatorConfig(PromptInfo promptInfo) {
+ static boolean isValidAuthenticatorConfig(Context context, PromptInfo promptInfo) {
final int authenticators = promptInfo.getAuthenticators();
- return isValidAuthenticatorConfig(authenticators);
+ return isValidAuthenticatorConfig(context, authenticators);
}
/**
- * Checks if the authenticator configuration is a valid combination of the public APIs
- * @param authenticators
- * @return
+ * Checks if the authenticator configuration is a valid combination of the public APIs.
+ *
+ * throws {@link SecurityException} if the caller requests for mandatory biometrics without
+ * {@link SET_BIOMETRIC_DIALOG_ADVANCED} permission
*/
- static boolean isValidAuthenticatorConfig(int authenticators) {
+ static boolean isValidAuthenticatorConfig(Context context, int authenticators) {
// The caller is not required to set the authenticators. But if they do, check the below.
if (authenticators == 0) {
return true;
@@ -271,6 +273,9 @@
} else if (biometricBits == Authenticators.BIOMETRIC_WEAK) {
return true;
} else if (isMandatoryBiometricsRequested(authenticators)) {
+ //TODO(b/347123256): Update CTS test
+ context.enforceCallingOrSelfPermission(SET_BIOMETRIC_DIALOG_ADVANCED,
+ "Must have SET_BIOMETRIC_DIALOG_ADVANCED permission");
return true;
}
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
index 5f2fbce..8a81aaa 100644
--- a/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
@@ -23,7 +23,10 @@
import com.android.server.SystemService;
-/** This class encapsulate the lifecycle methods of CrashRecovery module. */
+/** This class encapsulate the lifecycle methods of CrashRecovery module.
+ *
+ * @hide
+ */
public class CrashRecoveryModule {
private static final String TAG = "CrashRecoveryModule";
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 62d8761..03fec011 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1480,29 +1480,24 @@
brightnessState = clampScreenBrightness(brightnessState);
}
- if (useDozeBrightness) {
- // TODO(b/329676661): Introduce a config property to choose between this brightness
- // strategy and DOZE_DEFAULT
- // On some devices, when auto-brightness is disabled and the device is dozing, we use
- // the current brightness setting scaled by the doze scale factor
- if ((Float.isNaN(brightnessState)
- || displayBrightnessState.getDisplayBrightnessStrategyName()
- .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME))
- && mFlags.isDisplayOffloadEnabled()
- && mDisplayOffloadSession != null
+ if (useDozeBrightness && (Float.isNaN(brightnessState)
+ || displayBrightnessState.getDisplayBrightnessStrategyName()
+ .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME))) {
+ if (mFlags.isDisplayOffloadEnabled() && mDisplayOffloadSession != null
&& (mAutomaticBrightnessController == null
|| !mAutomaticBrightnessStrategy.shouldUseAutoBrightness())) {
+ // TODO(b/329676661): Introduce a config property to choose between this brightness
+ // strategy and DOZE_DEFAULT
+ // On some devices, when auto-brightness is disabled and the device is dozing, we
+ // use the current brightness setting scaled by the doze scale factor
rawBrightnessState = getDozeBrightnessForOffload();
brightnessState = clampScreenBrightness(rawBrightnessState);
updateScreenBrightnessSetting = false;
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_MANUAL);
mTempBrightnessEvent.setFlags(
mTempBrightnessEvent.getFlags() | BrightnessEvent.FLAG_DOZE_SCALE);
- }
-
- // Use default brightness when dozing unless overridden.
- if (Float.isNaN(brightnessState)
- && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()) {
+ } else if (!mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()) {
+ // Use default brightness when dozing unless overridden.
rawBrightnessState = mScreenBrightnessDozeConfig;
brightnessState = clampScreenBrightness(rawBrightnessState);
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index abb2132..06f419a 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -118,6 +118,37 @@
Intent.ACTION_MANAGED_PROFILE_AVAILABLE, new Pair<>(Intent.EXTRA_QUIET_MODE, false)
);
+ // Bits 1, 2, 3, 4 are already taken by: beep|buzz|blink|cooldown
+ static final int MUTE_REASON_NOT_MUTED = 0;
+ static final int MUTE_REASON_NOT_AUDIBLE = 1 << 5;
+ static final int MUTE_REASON_SILENT_UPDATE = 1 << 6;
+ static final int MUTE_REASON_POST_SILENTLY = 1 << 7;
+ static final int MUTE_REASON_LISTENER_HINT = 1 << 8;
+ static final int MUTE_REASON_DND = 1 << 9;
+ static final int MUTE_REASON_GROUP_ALERT = 1 << 10;
+ static final int MUTE_REASON_FLAG_SILENT = 1 << 11;
+ static final int MUTE_REASON_RATE_LIMIT = 1 << 12;
+ static final int MUTE_REASON_OTHER_INSISTENT_PLAYING = 1 << 13;
+ static final int MUTE_REASON_SUPPRESSED_BUBBLE = 1 << 14;
+ static final int MUTE_REASON_COOLDOWN = 1 << 15;
+
+ @IntDef(prefix = { "MUTE_REASON_" }, value = {
+ MUTE_REASON_NOT_MUTED,
+ MUTE_REASON_NOT_AUDIBLE,
+ MUTE_REASON_SILENT_UPDATE,
+ MUTE_REASON_POST_SILENTLY,
+ MUTE_REASON_LISTENER_HINT,
+ MUTE_REASON_DND,
+ MUTE_REASON_GROUP_ALERT,
+ MUTE_REASON_FLAG_SILENT,
+ MUTE_REASON_RATE_LIMIT,
+ MUTE_REASON_OTHER_INSISTENT_PLAYING,
+ MUTE_REASON_SUPPRESSED_BUBBLE,
+ MUTE_REASON_COOLDOWN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface MuteReason {}
+
private final Context mContext;
private final PackageManager mPackageManager;
private final TelephonyManager mTelephonyManager;
@@ -388,6 +419,7 @@
boolean buzz = false;
boolean beep = false;
boolean blink = false;
+ @MuteReason int shouldMuteReason = MUTE_REASON_NOT_MUTED;
final String key = record.getKey();
@@ -395,10 +427,6 @@
Log.d(TAG, "buzzBeepBlinkLocked " + record);
}
- if (isPoliteNotificationFeatureEnabled(record)) {
- mStrategy.onNotificationPosted(record);
- }
-
// Should this notification make noise, vibe, or use the LED?
final boolean aboveThreshold =
mIsAutomotive
@@ -443,7 +471,8 @@
boolean vibrateOnly =
hasValidVibrate && mNotificationCooldownVibrateUnlocked && mUserPresent;
boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
- if (hasAudibleAlert && !shouldMuteNotificationLocked(record, signals)) {
+ shouldMuteReason = shouldMuteNotificationLocked(record, signals, hasAudibleAlert);
+ if (shouldMuteReason == MUTE_REASON_NOT_MUTED) {
if (!sentAccessibilityEvent) {
sendAccessibilityEvent(record);
sentAccessibilityEvent = true;
@@ -541,15 +570,17 @@
}
}
final int buzzBeepBlinkLoggingCode =
- (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0) | getPoliteBit(record);
+ (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)
+ | getPoliteBit(record) | shouldMuteReason;
if (buzzBeepBlinkLoggingCode > 0) {
MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsEvent.NOTIFICATION_ALERT)
.setType(MetricsEvent.TYPE_OPEN)
.setSubtype(buzzBeepBlinkLoggingCode));
EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0,
- getPolitenessState(record));
+ getPolitenessState(record), shouldMuteReason);
}
+
if (Flags.politeNotifications()) {
// Update last alert time
if (buzz || beep) {
@@ -594,41 +625,46 @@
mNMP.getNotificationByKey(mVibrateNotificationKey));
}
- boolean shouldMuteNotificationLocked(final NotificationRecord record, final Signals signals) {
+ @MuteReason int shouldMuteNotificationLocked(final NotificationRecord record,
+ final Signals signals, boolean hasAudibleAlert) {
+ // Suppressed because no audible alert
+ if (!hasAudibleAlert) {
+ return MUTE_REASON_NOT_AUDIBLE;
+ }
// Suppressed because it's a silent update
final Notification notification = record.getNotification();
if (record.isUpdate && (notification.flags & FLAG_ONLY_ALERT_ONCE) != 0) {
- return true;
+ return MUTE_REASON_SILENT_UPDATE;
}
// Suppressed because a user manually unsnoozed something (or similar)
if (record.shouldPostSilently()) {
- return true;
+ return MUTE_REASON_POST_SILENTLY;
}
// muted by listener
final String disableEffects = disableNotificationEffects(record, signals.listenerHints);
if (disableEffects != null) {
ZenLog.traceDisableEffects(record, disableEffects);
- return true;
+ return MUTE_REASON_LISTENER_HINT;
}
// suppressed due to DND
if (record.isIntercepted()) {
- return true;
+ return MUTE_REASON_DND;
}
// Suppressed because another notification in its group handles alerting
if (record.getSbn().isGroup()) {
if (notification.suppressAlertingDueToGrouping()) {
- return true;
+ return MUTE_REASON_GROUP_ALERT;
}
}
// Suppressed because notification was explicitly flagged as silent
if (android.service.notification.Flags.notificationSilentFlag()) {
if (notification.isSilent()) {
- return true;
+ return MUTE_REASON_FLAG_SILENT;
}
}
@@ -636,12 +672,12 @@
final String pkg = record.getSbn().getPackageName();
if (mUsageStats.isAlertRateLimited(pkg)) {
Slog.e(TAG, "Muting recently noisy " + record.getKey());
- return true;
+ return MUTE_REASON_RATE_LIMIT;
}
// A different looping ringtone, such as an incoming call is playing
if (isCurrentlyInsistent() && !isInsistentUpdate(record)) {
- return true;
+ return MUTE_REASON_OTHER_INSISTENT_PLAYING;
}
// Suppressed since it's a non-interruptive update to a bubble-suppressed notification
@@ -650,11 +686,23 @@
if (record.isUpdate && !record.isInterruptive() && isBubbleOrOverflowed
&& record.getNotification().getBubbleMetadata() != null) {
if (record.getNotification().getBubbleMetadata().isNotificationSuppressed()) {
- return true;
+ return MUTE_REASON_SUPPRESSED_BUBBLE;
}
}
- return false;
+ if (isPoliteNotificationFeatureEnabled(record)) {
+ // Notify the politeness strategy that an alerting notification is posted
+ if (!isInsistentUpdate(record)) {
+ mStrategy.onNotificationPosted(record);
+ }
+
+ // Suppress if politeness is muted and it's not an update for insistent
+ if (getPolitenessState(record) == PolitenessStrategy.POLITE_STATE_MUTED) {
+ return MUTE_REASON_COOLDOWN;
+ }
+ }
+
+ return MUTE_REASON_NOT_MUTED;
}
private boolean isLoopingRingtoneNotification(final NotificationRecord playingRecord) {
@@ -1201,12 +1249,6 @@
mApplyPerPackage = applyPerPackage;
}
- boolean shouldIgnoreNotification(final NotificationRecord record) {
- // Ignore auto-group summaries => don't count them as app-posted notifications
- // for the cooldown budget
- return (record.getSbn().isGroup() && GroupHelper.isAggregatedGroup(record));
- }
-
/**
* Get the key that determines the grouping for the cooldown behavior.
*
@@ -1358,10 +1400,6 @@
@Override
public void onNotificationPosted(final NotificationRecord record) {
- if (shouldIgnoreNotification(record)) {
- return;
- }
-
long timeSinceLastNotif =
System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
@@ -1434,10 +1472,6 @@
@Override
void onNotificationPosted(NotificationRecord record) {
if (isAvalancheActive()) {
- if (shouldIgnoreNotification(record)) {
- return;
- }
-
long timeSinceLastNotif =
System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 79633f1..655f2e4 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6248,6 +6248,7 @@
int callingUid = Binder.getCallingUid();
@ZenModeConfig.ConfigOrigin int origin = computeZenOrigin(fromUser);
+ boolean isSystemCaller = isCallerSystemOrSystemUiOrShell();
boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi()
&& !canManageGlobalZenPolicy(pkg, callingUid);
@@ -6284,11 +6285,33 @@
policy.priorityCallSenders, policy.priorityMessageSenders,
policy.suppressedVisualEffects, currPolicy.priorityConversationSenders);
}
+
int newVisualEffects = calculateSuppressedVisualEffects(
policy, currPolicy, applicationInfo.targetSdkVersion);
- policy = new Policy(policy.priorityCategories,
- policy.priorityCallSenders, policy.priorityMessageSenders,
- newVisualEffects, policy.priorityConversationSenders);
+
+ if (android.app.Flags.modesUi()) {
+ // 1. Callers should not modify STATE_CHANNELS_BYPASSING_DND, which is
+ // internally calculated and only indicates whether channels that want to bypass
+ // DND _exist_.
+ // 2. Only system callers should modify STATE_PRIORITY_CHANNELS_BLOCKED because
+ // it is @hide.
+ // 3. If the policy has been modified by the targetSdkVersion checks above then
+ // it has lost its state flags and that's fine (STATE_PRIORITY_CHANNELS_BLOCKED
+ // didn't exist until V).
+ int newState = Policy.STATE_UNSET;
+ if (isSystemCaller && policy.state != Policy.STATE_UNSET) {
+ newState = Policy.policyState(
+ currPolicy.hasPriorityChannels(),
+ policy.allowPriorityChannels());
+ }
+ policy = new Policy(policy.priorityCategories,
+ policy.priorityCallSenders, policy.priorityMessageSenders,
+ newVisualEffects, newState, policy.priorityConversationSenders);
+ } else {
+ policy = new Policy(policy.priorityCategories,
+ policy.priorityCallSenders, policy.priorityMessageSenders,
+ newVisualEffects, policy.priorityConversationSenders);
+ }
if (shouldApplyAsImplicitRule) {
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy);
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index f78c448..d206c66 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -99,6 +99,7 @@
// True if needing to roll back only rebootless apexes when native crash happens
private boolean mTwoPhaseRollbackEnabled;
+ /** @hide */
@VisibleForTesting
public RollbackPackageHealthObserver(Context context, ApexManager apexManager) {
mContext = context;
@@ -123,7 +124,7 @@
}
}
- RollbackPackageHealthObserver(Context context) {
+ public RollbackPackageHealthObserver(@NonNull Context context) {
this(context, ApexManager.getInstance());
}
@@ -239,8 +240,8 @@
return false;
}
-
@Override
+ @NonNull
public String getUniqueIdentifier() {
return NAME;
}
@@ -251,7 +252,7 @@
}
@Override
- public boolean mayObservePackage(String packageName) {
+ public boolean mayObservePackage(@NonNull String packageName) {
if (getAvailableRollbacks().isEmpty()) {
return false;
}
@@ -281,12 +282,14 @@
* This may cause {@code packages} to be rolled back if they crash too freqeuntly.
*/
@AnyThread
- void startObservingHealth(List<String> packages, long durationMs) {
+ @NonNull
+ public void startObservingHealth(@NonNull List<String> packages, @NonNull long durationMs) {
PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
}
@AnyThread
- void notifyRollbackAvailable(RollbackInfo rollback) {
+ @NonNull
+ public void notifyRollbackAvailable(@NonNull RollbackInfo rollback) {
mHandler.post(() -> {
// Enable two-phase rollback when a rebootless apex rollback is made available.
// We assume the rebootless apex is stable and is less likely to be the cause
@@ -314,7 +317,7 @@
* to check for native crashes and mitigate them if needed.
*/
@AnyThread
- void onBootCompletedAsync() {
+ public void onBootCompletedAsync() {
mHandler.post(()->onBootCompleted());
}
diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
index 79560ce..9cfed02 100644
--- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -51,6 +51,7 @@
/**
* This class handles the logic for logging Watchdog-triggered rollback events.
+ * @hide
*/
public final class WatchdogRollbackLogger {
private static final String TAG = "WatchdogRollbackLogger";
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4754ffb..946b61a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1468,7 +1468,7 @@
|| change == PACKAGE_TEMPORARY_CHANGE) {
changed = true;
if (doit) {
- Slog.w(TAG, "Wallpaper uninstalled, removing: "
+ Slog.e(TAG, "Wallpaper uninstalled, removing: "
+ wallpaper.getComponent());
clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
}
@@ -1491,7 +1491,7 @@
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
} catch (NameNotFoundException e) {
- Slog.w(TAG, "Wallpaper component gone, removing: "
+ Slog.e(TAG, "Wallpaper component gone, removing: "
+ wallpaper.getComponent());
clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 12d733f..14e9180 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2154,7 +2154,10 @@
}
mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);
- mActivityRecordInputSink = new ActivityRecordInputSink(this, sourceRecord);
+ final boolean appOptInTouchPassThrough =
+ options != null && options.isAllowPassThroughOnTouchOutside();
+ mActivityRecordInputSink = new ActivityRecordInputSink(
+ this, sourceRecord, appOptInTouchPassThrough);
mAppActivityEmbeddingSplitsEnabled = isAppActivityEmbeddingSplitsEnabled();
mAllowUntrustedEmbeddingStateSharing = getAllowUntrustedEmbeddingStateSharingProperty();
@@ -3171,14 +3174,23 @@
return getWindowConfiguration().canReceiveKeys() && !mWaitForEnteringPinnedMode;
}
- boolean isResizeable() {
- return isResizeable(/* checkPictureInPictureSupport */ true);
+ /**
+ * Returns {@code true} if the fixed orientation, aspect ratio, resizability of this activity
+ * will be ignored.
+ */
+ boolean isUniversalResizeable() {
+ return mWmService.mConstants.mIgnoreActivityOrientationRequest
+ && info.applicationInfo.category != ApplicationInfo.CATEGORY_GAME
+ // If the user preference respects aspect ratio, then it becomes non-resizable.
+ && !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
+ .shouldApplyUserMinAspectRatioOverride();
}
- boolean isResizeable(boolean checkPictureInPictureSupport) {
+ boolean isResizeable() {
return mAtmService.mForceResizableActivities
|| ActivityInfo.isResizeableMode(info.resizeMode)
- || (info.supportsPictureInPicture() && checkPictureInPictureSupport)
+ || info.supportsPictureInPicture()
+ || isUniversalResizeable()
// If the activity can be embedded, it should inherit the bounds of task fragment.
|| isEmbedded();
}
@@ -8162,11 +8174,8 @@
@Override
@ActivityInfo.ScreenOrientation
protected int getOverrideOrientation() {
- final int candidateOrientation;
- if (!mWmService.mConstants.mIgnoreActivityOrientationRequest
- || info.applicationInfo.category == ApplicationInfo.CATEGORY_GAME) {
- candidateOrientation = super.getOverrideOrientation();
- } else {
+ int candidateOrientation = super.getOverrideOrientation();
+ if (isUniversalResizeable() && ActivityInfo.isFixedOrientation(candidateOrientation)) {
candidateOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
return mAppCompatController.getOrientationPolicy()
@@ -10025,7 +10034,7 @@
}
StringBuilder sb = new StringBuilder(128);
sb.append("ActivityRecord{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(System.identityHashCode(this));
sb.append(" u");
sb.append(mUserId);
sb.append(' ');
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index 1a19787..fa5beca 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -16,13 +16,18 @@
package com.android.server.wm;
+import android.app.ActivityOptions;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.os.Build;
import android.os.InputConfig;
import android.view.InputWindowHandle;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import com.android.window.flags.Flags;
+
/**
* Creates a InputWindowHandle that catches all touches that would otherwise pass through an
* Activity.
@@ -35,6 +40,21 @@
@ChangeId
static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L;
+ // TODO(b/369605358) Update EnabledSince when SDK 36 version code is available.
+ /**
+ * If the app's target SDK is 36+, pass-through touches from a cross-uid overlaying activity is
+ * blocked by default. The activity may opt in to receive pass-through touches using
+ * {@link ActivityOptions#setAllowPassThroughOnTouchOutside}, which allows the to-be-launched
+ * cross-uid overlaying activity and other activities in that app to pass through touches. The
+ * activity needs to ensure that it trusts the overlaying app and its content is not vulnerable
+ * to UI redressing attacks.
+ *
+ * @see ActivityOptions#setAllowPassThroughOnTouchOutside
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT)
+ static final long ENABLE_OVERLAY_TOUCH_PASS_THROUGH_OPT_IN_ENFORCEMENT = 358129114L;
+
private final ActivityRecord mActivityRecord;
private final boolean mIsCompatEnabled;
private final String mName;
@@ -42,13 +62,24 @@
private InputWindowHandleWrapper mInputWindowHandleWrapper;
private SurfaceControl mSurfaceControl;
- ActivityRecordInputSink(ActivityRecord activityRecord, ActivityRecord sourceRecord) {
+ ActivityRecordInputSink(ActivityRecord activityRecord, ActivityRecord sourceRecord,
+ boolean appOptInTouchPassThrough) {
mActivityRecord = activityRecord;
mIsCompatEnabled = CompatChanges.isChangeEnabled(ENABLE_TOUCH_OPAQUE_ACTIVITIES,
mActivityRecord.getUid());
mName = Integer.toHexString(System.identityHashCode(this)) + " ActivityRecordInputSink "
+ mActivityRecord.mActivityComponent.flattenToShortString();
- if (sourceRecord != null) {
+
+ if (sourceRecord == null) {
+ return;
+ }
+ // If the source activity has target sdk 36+, it is required to opt in to receive
+ // pass-through touches from the overlaying activity.
+ final boolean isTouchPassThroughOptInEnforced = CompatChanges.isChangeEnabled(
+ ENABLE_OVERLAY_TOUCH_PASS_THROUGH_OPT_IN_ENFORCEMENT,
+ sourceRecord.getUid());
+ if (!Flags.touchPassThroughOptIn() || !isTouchPassThroughOptInEnforced
+ || appOptInTouchPassThrough) {
sourceRecord.mAllowedTouchUid = mActivityRecord.getUid();
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d29ff54..2ba300a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -100,6 +100,7 @@
import android.app.WaitResult;
import android.app.WindowConfiguration;
import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
import android.content.IIntentSender;
import android.content.Intent;
@@ -182,7 +183,7 @@
* Feature flag for go/activity-security rules
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @Disabled
static final long ASM_RESTRICTIONS = 230590090L;
private final ActivityTaskManagerService mService;
@@ -1028,6 +1029,7 @@
if (requestCode >= 0 && !sourceRecord.finishing) {
resultRecord = sourceRecord;
}
+ request.logMessage.append(" (sr=" + System.identityHashCode(sourceRecord) + ")");
}
}
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 51ef87d..6946b6a 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -114,9 +114,6 @@
return mTransparentPolicy.getInheritedMinAspectRatio();
}
final ActivityInfo info = mActivityRecord.info;
- if (info.applicationInfo == null) {
- return info.getMinAspectRatio();
- }
final AppCompatAspectRatioOverrides aspectRatioOverrides =
mAppCompatOverrides.getAppCompatAspectRatioOverrides();
if (aspectRatioOverrides.shouldApplyUserMinAspectRatioOverride()) {
@@ -128,6 +125,9 @@
mActivityRecord);
if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
&& !shouldOverrideMinAspectRatioForCamera) {
+ if (mActivityRecord.isUniversalResizeable()) {
+ return 0;
+ }
return info.getMinAspectRatio();
}
@@ -170,6 +170,9 @@
if (mTransparentPolicy.isRunning()) {
return mTransparentPolicy.getInheritedMaxAspectRatio();
}
+ if (mActivityRecord.isUniversalResizeable()) {
+ return 0;
+ }
return mActivityRecord.info.getMaxAspectRatio();
}
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index 1924691..6e6f76a 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -188,10 +188,6 @@
}
final ActivityInfo info = mActivityRecord.info;
- if (info.applicationInfo == null) {
- return info.getMinAspectRatio();
- }
-
final AppCompatAspectRatioOverrides aspectRatioOverrides =
mAppCompatOverrides.getAppCompatAspectRatioOverrides();
if (shouldApplyUserMinAspectRatioOverride(task)) {
@@ -203,6 +199,9 @@
&& dc.mAppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord);
if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
&& !shouldOverrideMinAspectRatioForCamera) {
+ if (mActivityRecord.isUniversalResizeable()) {
+ return 0;
+ }
return info.getMinAspectRatio();
}
@@ -246,6 +245,9 @@
if (mTransparentPolicy.isRunning()) {
return mTransparentPolicy.getInheritedMaxAspectRatio();
}
+ if (mActivityRecord.isUniversalResizeable()) {
+ return 0;
+ }
return mActivityRecord.info.getMaxAspectRatio();
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e6f6215..1ac0bb0 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2156,6 +2156,11 @@
}
mDecorInsets.invalidate();
mDecorInsets.mInfoForRotation[rotation].set(newInfo);
+ if (!mService.mDisplayEnabled) {
+ // There could be other pending changes during booting. It might be better to let the
+ // clients receive the new states earlier.
+ return true;
+ }
return !sameConfigFrame;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 14f034b..edbc328 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1475,7 +1475,7 @@
// The starting window should keep covering its task when a pure TaskFragment is added
// because its bounds may not fill the task.
final ActivityRecord top = getTopMostActivity();
- if (top != null) {
+ if (top != null && !top.hasFixedRotationTransform()) {
top.associateStartingWindowWithTaskIfNeeded();
}
}
@@ -4707,8 +4707,13 @@
// If the moveToFront is a part of finishing transition, then make sure
// the z-order of tasks are up-to-date.
if (topActivity.mTransitionController.inFinishingTransition(topActivity)) {
- Transition.assignLayers(taskDisplayArea,
- taskDisplayArea.getPendingTransaction());
+ final SurfaceControl.Transaction tx =
+ taskDisplayArea.getPendingTransaction();
+ Transition.assignLayers(taskDisplayArea, tx);
+ final SurfaceControl leash = topActivity.getFixedRotationLeash();
+ if (leash != null) {
+ tx.setLayer(leash, topActivity.getLastLayer());
+ }
}
}
}
diff --git a/services/profcollect/src/com/android/server/profcollect/Utils.java b/services/profcollect/src/com/android/server/profcollect/Utils.java
index 8508802..b4e2544 100644
--- a/services/profcollect/src/com/android/server/profcollect/Utils.java
+++ b/services/profcollect/src/com/android/server/profcollect/Utils.java
@@ -19,6 +19,7 @@
import static com.android.server.profcollect.ProfcollectForwardingService.LOG_TAG;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.provider.DeviceConfig;
import android.util.Log;
@@ -42,7 +43,7 @@
BackgroundThread.get().getThreadHandler().post(() -> {
try {
mIProfcollect.trace_system(eventName);
- } catch (RemoteException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
});
@@ -56,7 +57,7 @@
BackgroundThread.get().getThreadHandler().postDelayed(() -> {
try {
mIProfcollect.trace_system(eventName);
- } catch (RemoteException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
}, delayMs);
@@ -73,10 +74,10 @@
mIProfcollect.trace_process(eventName,
processName,
durationMs);
- } catch (RemoteException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
});
return true;
}
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
index 14cb22d..efc2d97 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -16,12 +16,20 @@
package com.android.server.biometrics;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+
+import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
@@ -36,8 +44,12 @@
import androidx.test.filters.SmallTest;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
@Presubmit
@SmallTest
@@ -45,6 +57,17 @@
@Rule
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public MockitoRule mockitorule = MockitoJUnit.rule();
+
+ @Mock
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission(
+ eq(SET_BIOMETRIC_DIALOG_ADVANCED), any());
+ }
@Test
public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andKeyAuthenticators() {
@@ -162,28 +185,39 @@
@Test
public void testIsValidAuthenticatorConfig() {
- assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.EMPTY_SET));
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.EMPTY_SET));
- assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_STRONG));
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.BIOMETRIC_STRONG));
- assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_WEAK));
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.BIOMETRIC_WEAK));
- assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL));
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.DEVICE_CREDENTIAL));
- assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.DEVICE_CREDENTIAL
| Authenticators.BIOMETRIC_STRONG));
- assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.DEVICE_CREDENTIAL
| Authenticators.BIOMETRIC_WEAK));
- assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_CONVENIENCE));
+ assertFalse(Utils.isValidAuthenticatorConfig(
+ mContext, Authenticators.BIOMETRIC_CONVENIENCE));
- assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_CONVENIENCE
+ assertFalse(Utils.isValidAuthenticatorConfig(mContext, Authenticators.BIOMETRIC_CONVENIENCE
| Authenticators.DEVICE_CREDENTIAL));
- assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_MAX_STRENGTH));
+ assertFalse(Utils.isValidAuthenticatorConfig(
+ mContext, Authenticators.BIOMETRIC_MAX_STRENGTH));
- assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_MIN_STRENGTH));
+ assertFalse(Utils.isValidAuthenticatorConfig(
+ mContext, Authenticators.BIOMETRIC_MIN_STRENGTH));
+
+ assertThrows(SecurityException.class, () -> Utils.isValidAuthenticatorConfig(
+ mContext, Authenticators.MANDATORY_BIOMETRICS));
+
+ doNothing().when(mContext).enforceCallingOrSelfPermission(
+ eq(SET_BIOMETRIC_DIALOG_ADVANCED), any());
+
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.MANDATORY_BIOMETRICS));
// The rest of the bits are not allowed to integrate with the public APIs
for (int i = 8; i < 32; i++) {
@@ -192,7 +226,7 @@
|| authenticator == Authenticators.MANDATORY_BIOMETRICS) {
continue;
}
- assertFalse(Utils.isValidAuthenticatorConfig(1 << i));
+ assertFalse(Utils.isValidAuthenticatorConfig(mContext, 1 << i));
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 62e5b9a..45cd571 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -31,6 +31,12 @@
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_COOLDOWN;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_FLAG_SILENT;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_GROUP_ALERT;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_NOT_MUTED;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_OTHER_INSISTENT_PLAYING;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -106,6 +112,7 @@
import com.android.internal.config.sysui.TestableFlagResolver;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.InstanceIdSequenceFake;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.IntPair;
import com.android.server.UiServiceTestCase;
import com.android.server.lights.LightsManager;
@@ -1276,7 +1283,8 @@
verifyNeverBeep();
assertFalse(r.isInterruptive());
assertEquals(-1, r.getLastAudiblyAlertedMs());
- assertTrue(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS,
+ true)).isEqualTo(MUTE_REASON_FLAG_SILENT);
}
@Test
@@ -1295,7 +1303,8 @@
verifyNeverBeep();
assertFalse(r.isInterruptive());
assertEquals(-1, r.getLastAudiblyAlertedMs());
- assertTrue(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS,
+ true)).isEqualTo(MUTE_REASON_GROUP_ALERT);
}
@Test
@@ -1861,7 +1870,9 @@
verifyBeepLooped();
NotificationRecord interrupter = getBeepyOtherNotification();
- assertTrue(mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS));
+ assertThat(
+ mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS,
+ true)).isEqualTo(MUTE_REASON_OTHER_INSISTENT_PLAYING);
mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS);
verifyBeep(1);
@@ -1879,16 +1890,16 @@
ringtoneChannel.enableVibration(true);
NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
mService.addNotification(ringtoneNotification);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
verifyBeepLooped();
verifyDelayedVibrateLooped();
Mockito.reset(mVibrator);
Mockito.reset(mRingtonePlayer);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
// beep wasn't reset
@@ -1907,8 +1918,8 @@
ringtoneChannel.enableVibration(true);
NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
mService.addNotification(ringtoneNotification);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
verifyBeepLooped();
verifyDelayedVibrateLooped();
@@ -1930,8 +1941,8 @@
ringtoneChannel.enableVibration(true);
NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
mService.addNotification(ringtoneNotification);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
verifyBeepLooped();
verifyNeverVibrate();
@@ -1951,14 +1962,15 @@
new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
ringtoneChannel.enableVibration(true);
NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
verifyVibrateLooped();
NotificationRecord interrupter = getBuzzyOtherNotification();
- assertTrue(mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(interrupter,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_OTHER_INSISTENT_PLAYING);
mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS);
verifyVibrate(1);
@@ -2260,10 +2272,13 @@
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS, true))
+ .isEqualTo(MUTE_REASON_COOLDOWN);
- verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+ verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@@ -2305,8 +2320,9 @@
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
assertEquals(-1, r3.getLastAudiblyAlertedMs());
@@ -2381,9 +2397,10 @@
false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
// update should beep at 0% volume
- mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
assertEquals(-1, r2.getLastAudiblyAlertedMs());
- verifyBeepVolume(0.0f);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
// Use different package for next notifications
NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
@@ -2392,8 +2409,9 @@
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
assertEquals(-1, r3.getLastAudiblyAlertedMs());
@@ -2493,8 +2511,9 @@
// Regular notification: should beep at 0% volume
NotificationRecord r = getBeepyNotification();
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
assertEquals(-1, r.getLastAudiblyAlertedMs());
Mockito.reset(mRingtonePlayer);
@@ -2525,8 +2544,9 @@
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
// Set important conversation
mChannel.setImportantConversation(true);
@@ -2751,9 +2771,10 @@
Mockito.reset(mRingtonePlayer);
// next update at 0% volume
- mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
assertEquals(-1, summary.getLastAudiblyAlertedMs());
- verifyBeepVolume(0.0f);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
}
@@ -2823,9 +2844,10 @@
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
assertEquals(-1, r2.getLastAudiblyAlertedMs());
- verifyBeepVolume(0.0f);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
// Use different package for next notifications
NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
@@ -2891,6 +2913,94 @@
}
@Test
+ public void testBeepVolume_politeNotif_groupAlertSummary() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ // NOTIFICATION_COOLDOWN_ALL setting is enabled
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+ initAttentionHelper(flagResolver);
+
+ // child should beep at 0% volume
+ NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(child.isInterruptive());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // child should beep at 0% volume
+ child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(child.isInterruptive());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // summary 100% volume (GROUP_ALERT_SUMMARY)
+ NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+ summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+ Mockito.reset(mRingtonePlayer);
+
+ // next update at 50% volume because only summary was tracked as alerting
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+ verifyBeepVolume(0.5f);
+
+ verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
+ public void testBeepVolume_politeNotif_groupAlertChildren() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ // NOTIFICATION_COOLDOWN_ALL setting is enabled
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+ initAttentionHelper(flagResolver);
+
+ // summary 0% volume (GROUP_ALERT_CHILDREN)
+ NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+ summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(summary.isInterruptive());
+ assertEquals(-1, summary.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // child should beep at 100% volume
+ NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+ Mockito.reset(mRingtonePlayer);
+
+ // child should beep at 50% volume
+ child = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+ verifyBeepVolume(0.5f);
+ Mockito.reset(mRingtonePlayer);
+
+ // child should beep at 0% volume
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertTrue(child.isInterruptive());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
+
+ verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
public void testVibrationIntensity_politeNotif() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2914,8 +3024,9 @@
Mockito.reset(vibratorHelper);
// 2nd update should buzz at 0% intensity
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
- verify(vibratorHelper, times(1)).scale(any(), eq(0.0f));
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyNeverVibrate();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
}
@Test
@@ -3007,10 +3118,11 @@
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
- verifyBeepVolume(0.0f);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
- verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+ verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
assertEquals(-1, r.getLastAudiblyAlertedMs());
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 44770d2..bbf2cbd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -193,6 +193,7 @@
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
import android.app.Person;
import android.app.RemoteInput;
@@ -655,7 +656,8 @@
when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt()))
.thenReturn(INVALID_TASK_ID);
mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
- when(mUm.getProfileIds(eq(mUserId), eq(false))).thenReturn(new int[] { mUserId });
+ when(mUm.getProfileIds(eq(mUserId), anyBoolean())).thenReturn(new int[]{mUserId});
+ when(mUmInternal.getProfileIds(eq(mUserId), anyBoolean())).thenReturn(new int[]{mUserId});
when(mAmi.getCurrentUserId()).thenReturn(mUserId);
when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(true);
@@ -15971,6 +15973,57 @@
assertThat(updatedRule.getValue().isEnabled()).isFalse();
}
+ @Test
+ @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI})
+ public void setNotificationPolicy_fromSystemApp_appliesPriorityChannelsAllowed()
+ throws Exception {
+ setUpRealZenTest();
+ // Start with hasPriorityChannels=true, allowPriorityChannels=true ("default").
+ mService.mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0, 0,
+ Policy.policyState(true, true), 0),
+ ZenModeConfig.ORIGIN_SYSTEM, Process.SYSTEM_UID);
+
+ // The caller will supply states with "wrong" hasPriorityChannels.
+ int stateBlockingPriorityChannels = Policy.policyState(false, false);
+ mBinderService.setNotificationPolicy(mPkg,
+ new Policy(1, 0, 0, 0, stateBlockingPriorityChannels, 0), false);
+
+ // hasPriorityChannels is untouched and allowPriorityChannels was updated.
+ assertThat(mBinderService.getNotificationPolicy(mPkg).priorityCategories).isEqualTo(1);
+ assertThat(mBinderService.getNotificationPolicy(mPkg).state).isEqualTo(
+ Policy.policyState(true, false));
+
+ // Same but setting allowPriorityChannels to true.
+ int stateAllowingPriorityChannels = Policy.policyState(false, true);
+ mBinderService.setNotificationPolicy(mPkg,
+ new Policy(2, 0, 0, 0, stateAllowingPriorityChannels, 0), false);
+
+ assertThat(mBinderService.getNotificationPolicy(mPkg).priorityCategories).isEqualTo(2);
+ assertThat(mBinderService.getNotificationPolicy(mPkg).state).isEqualTo(
+ Policy.policyState(true, true));
+ }
+
+ @Test
+ @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI})
+ @DisableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setNotificationPolicy_fromRegularAppThatCanModifyPolicy_ignoresState()
+ throws Exception {
+ setUpRealZenTest();
+ // Start with hasPriorityChannels=true, allowPriorityChannels=true ("default").
+ mService.mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0, 0,
+ Policy.policyState(true, true), 0),
+ ZenModeConfig.ORIGIN_SYSTEM, Process.SYSTEM_UID);
+ mService.setCallerIsNormalPackage();
+
+ mBinderService.setNotificationPolicy(mPkg,
+ new Policy(1, 0, 0, 0, Policy.policyState(false, false), 0), false);
+
+ // Policy was updated but the attempt to change state was ignored (it's a @hide API).
+ assertThat(mBinderService.getNotificationPolicy(mPkg).priorityCategories).isEqualTo(1);
+ assertThat(mBinderService.getNotificationPolicy(mPkg).state).isEqualTo(
+ Policy.policyState(true, true));
+ }
+
/** Prepares for a zen-related test that uses the real {@link ZenModeHelper}. */
private void setUpRealZenTest() throws Exception {
when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index 5787780..4cd75d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -308,6 +308,8 @@
// KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE
case "android.activity.launchCookie": // KEY_LAUNCH_COOKIE
case "android:activity.animAbortListener": // KEY_ANIM_ABORT_LISTENER
+ case "android.activity.allowPassThroughOnTouchOutside":
+ // KEY_ALLOW_PASS_THROUGH_ON_TOUCH_OUTSIDE
// Existing keys
break;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 8fa4667..adc969c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4849,6 +4849,39 @@
}
@Test
+ public void testUniversalResizeable() {
+ mWm.mConstants.mIgnoreActivityOrientationRequest = true;
+ setUpApp(mDisplayContent);
+ final float maxAspect = 1.8f;
+ final float minAspect = 1.5f;
+ prepareLimitedBounds(mActivity, maxAspect, minAspect,
+ ActivityInfo.SCREEN_ORIENTATION_LOCKED, true /* isUnresizable */);
+
+ assertTrue(mActivity.isUniversalResizeable());
+ assertTrue(mActivity.isResizeable());
+ assertFalse(mActivity.shouldCreateAppCompatDisplayInsets());
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
+ assertEquals(mActivity.getTask().getBounds(), mActivity.getBounds());
+ final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivity.mAppCompatController
+ .getAppCompatAspectRatioPolicy();
+ assertEquals(0, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
+ assertEquals(0, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);
+
+ // Compat override can still take effect.
+ final AppCompatAspectRatioOverrides aspectRatioOverrides =
+ mActivity.mAppCompatController.getAppCompatAspectRatioOverrides();
+ spyOn(aspectRatioOverrides);
+ doReturn(true).when(aspectRatioOverrides).shouldOverrideMinAspectRatio();
+ assertEquals(minAspect, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);
+
+ // User override can still take effect.
+ doReturn(true).when(aspectRatioOverrides).shouldApplyUserMinAspectRatioOverride();
+ assertFalse(mActivity.isResizeable());
+ assertEquals(maxAspect, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
+ assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
+ }
+
+ @Test
public void testClearSizeCompat_resetOverrideConfig() {
final int origDensity = 480;
final int newDensity = 520;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 3e226cc..92effe0 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -19426,4 +19426,48 @@
return "UNKNOWN(" + state + ")";
}
}
+
+ /**
+ * This API can be used by only CTS to override the Euicc UI component.
+ *
+ * @param componentName ui component to be launched for testing. {@code null} to reset.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setTestEuiccUiComponent(@Nullable ComponentName componentName) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) {
+ Rlog.e(TAG, "setTestEuiccUiComponent(): ITelephony instance is NULL");
+ throw new IllegalStateException("Telephony service not available.");
+ }
+ telephony.setTestEuiccUiComponent(componentName);
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "setTestEuiccUiComponent() RemoteException : " + ex);
+ throw ex.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * This API can be used by only CTS to retrieve the Euicc UI component.
+ *
+ * @return The Euicc UI component for testing. {@code null} if not available.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @Nullable
+ public ComponentName getTestEuiccUiComponent() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) {
+ Rlog.e(TAG, "getTestEuiccUiComponent(): ITelephony instance is NULL");
+ throw new IllegalStateException("Telephony service not available.");
+ }
+ return telephony.getTestEuiccUiComponent();
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "getTestEuiccUiComponent() RemoteException : " + ex);
+ throw ex.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 44d3fca..567314b 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -128,6 +128,12 @@
/** APN type for RCS (Rich Communication Services). */
@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final int TYPE_RCS = ApnTypes.RCS;
+ /** APN type for OEM_PAID networks (Automotive PANS) */
+ @FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE)
+ public static final int TYPE_OEM_PAID = 1 << 16; // TODO(b/366194627): ApnTypes.OEM_PAID;
+ /** APN type for OEM_PRIVATE networks (Automotive PANS) */
+ @FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE)
+ public static final int TYPE_OEM_PRIVATE = 1 << 17; // TODO(b/366194627): ApnTypes.OEM_PRIVATE;
/** @hide */
@IntDef(flag = true, prefix = {"TYPE_"}, value = {
@@ -146,7 +152,9 @@
TYPE_BIP,
TYPE_VSIM,
TYPE_ENTERPRISE,
- TYPE_RCS
+ TYPE_RCS,
+ TYPE_OEM_PAID,
+ TYPE_OEM_PRIVATE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApnType {
@@ -375,6 +383,27 @@
@SystemApi
public static final String TYPE_RCS_STRING = "rcs";
+ /**
+ * APN type for OEM_PAID networks (Automotive PANS)
+ *
+ * Note: String representations of APN types are intended for system apps to communicate with
+ * modem components or carriers. Non-system apps should use the integer variants instead.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE)
+ @SystemApi
+ public static final String TYPE_OEM_PAID_STRING = "oem_paid";
+
+ /**
+ * APN type for OEM_PRIVATE networks (Automotive PANS)
+ *
+ * Note: String representations of APN types are intended for system apps to communicate with
+ * modem components or carriers. Non-system apps should use the integer variants instead.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE)
+ @SystemApi
+ public static final String TYPE_OEM_PRIVATE_STRING = "oem_private";
/** @hide */
@IntDef(prefix = { "AUTH_TYPE_" }, value = {
@@ -489,6 +518,8 @@
APN_TYPE_STRING_MAP.put(TYPE_VSIM_STRING, TYPE_VSIM);
APN_TYPE_STRING_MAP.put(TYPE_BIP_STRING, TYPE_BIP);
APN_TYPE_STRING_MAP.put(TYPE_RCS_STRING, TYPE_RCS);
+ APN_TYPE_STRING_MAP.put(TYPE_OEM_PAID_STRING, TYPE_OEM_PAID);
+ APN_TYPE_STRING_MAP.put(TYPE_OEM_PRIVATE_STRING, TYPE_OEM_PRIVATE);
APN_TYPE_INT_MAP = new ArrayMap<>();
APN_TYPE_INT_MAP.put(TYPE_DEFAULT, TYPE_DEFAULT_STRING);
@@ -507,6 +538,8 @@
APN_TYPE_INT_MAP.put(TYPE_VSIM, TYPE_VSIM_STRING);
APN_TYPE_INT_MAP.put(TYPE_BIP, TYPE_BIP_STRING);
APN_TYPE_INT_MAP.put(TYPE_RCS, TYPE_RCS_STRING);
+ APN_TYPE_INT_MAP.put(TYPE_OEM_PAID, TYPE_OEM_PAID_STRING);
+ APN_TYPE_INT_MAP.put(TYPE_OEM_PRIVATE, TYPE_OEM_PRIVATE_STRING);
PROTOCOL_STRING_MAP = new ArrayMap<>();
PROTOCOL_STRING_MAP.put("IP", PROTOCOL_IP);
@@ -2383,7 +2416,8 @@
public ApnSetting build() {
if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI
| TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX
- | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE | TYPE_RCS)) == 0
+ | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE | TYPE_RCS | TYPE_OEM_PAID
+ | TYPE_OEM_PRIVATE)) == 0
|| TextUtils.isEmpty(mApnName) || TextUtils.isEmpty(mEntryName)) {
return null;
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index e57c207..7f25ef2 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3017,6 +3017,14 @@
boolean setSatelliteListeningTimeoutDuration(in long timeoutMillis);
/**
+ * This API can be used by only CTS to control ingoring cellular service state event.
+ *
+ * @param enabled Whether to enable boolean config.
+ * @return {@code true} if the value is set successfully, {@code false} otherwise.
+ */
+ boolean setSatelliteIgnoreCellularServiceState(in boolean enabled);
+
+ /**
* This API can be used by only CTS to update satellite pointing UI app package and class names.
*
* @param packageName The package name of the satellite pointing UI app.
@@ -3409,4 +3417,20 @@
* @hide
*/
boolean setSatelliteSubscriberIdListChangedIntentComponent(in String name);
+
+ /**
+ * This API can be used by only CTS to override the Euicc UI component.
+ *
+ * @param componentName ui component to be launched for testing
+ * @hide
+ */
+ void setTestEuiccUiComponent(in ComponentName componentName);
+
+ /**
+ * This API can be used by only CTS to retrieve the Euicc UI component.
+ *
+ * @return The Euicc UI component for testing.
+ * @hide
+ */
+ ComponentName getTestEuiccUiComponent();
}
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
index 82de070..8b65efd 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
index 4ffb11a..3382c1e 100644
--- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
index 0fa4d07..e941e79 100644
--- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
index 4d9fefb..4e06dca 100644
--- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index b879c54..0cadd68 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- enable AOD -->
<option name="set-secure-setting" key="doze_always_on" value="1" />
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
index 04b312a..f32e8bed 100644
--- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
index 8acdabc..68ae4f1 100644
--- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index 91ece21..ec186723 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->