Merge "[KTF integration] Fix AodAlphaViewModel" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index ab2b110..ab5d503 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -76,7 +76,6 @@
"android.widget.flags-aconfig-java",
"backstage_power_flags_lib",
"backup_flags_lib",
- "bluetooth_exported_flags_java_lib",
"camera_platform_flags_core_java_lib",
"com.android.hardware.input-aconfig-java",
"com.android.input.flags-aconfig-java",
diff --git a/Android.bp b/Android.bp
index af312bf..d6b303f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -425,7 +425,6 @@
"sounddose-aidl-java",
"modules-utils-expresslog",
"perfetto_trace_javastream_protos_jarjar",
- "libaconfig_java_proto_nano",
],
}
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 76d6547..746ec2e 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -843,6 +843,15 @@
return this;
}
+ /**
+ * Sets the package that owns this rule
+ * @hide
+ */
+ public @NonNull Builder setPackage(@NonNull String pkg) {
+ mPkg = pkg;
+ return this;
+ }
+
public @NonNull AutomaticZenRule build() {
AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity,
mConditionId, mPolicy, mInterruptionFilter, mEnabled);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index e3380e0..3b9a5d3 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -19,6 +19,7 @@
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.StrictMode.vmIncorrectContextUseEnabled;
+import static android.permission.flags.Flags.shouldRegisterAttributionSource;
import static android.view.WindowManager.LayoutParams.WindowType;
import android.Manifest;
@@ -3157,7 +3158,8 @@
int deviceId = vdm.getDeviceIdForDisplayId(displayId);
if (deviceId != mDeviceId) {
mDeviceId = deviceId;
- mAttributionSource = mAttributionSource.withDeviceId(mDeviceId);
+ mAttributionSource =
+ createAttributionSourceWithDeviceId(mAttributionSource, mDeviceId);
notifyOnDeviceChangedListeners(mDeviceId);
}
}
@@ -3180,6 +3182,7 @@
if (mDeviceId != updatedDeviceId) {
mDeviceId = updatedDeviceId;
+ mAttributionSource = createAttributionSourceWithDeviceId(mAttributionSource, mDeviceId);
notifyOnDeviceChangedListeners(updatedDeviceId);
}
}
@@ -3548,8 +3551,22 @@
deviceId, nextAttributionSource);
// If we want to access protected data on behalf of another app we need to
// tell the OS that we opt in to participate in the attribution chain.
- if (nextAttributionSource != null || shouldRegister) {
- attributionSource = getSystemService(PermissionManager.class)
+ return registerAttributionSourceIfNeeded(attributionSource, shouldRegister);
+ }
+
+ private @NonNull AttributionSource createAttributionSourceWithDeviceId(
+ @NonNull AttributionSource oldSource, int deviceId) {
+ boolean shouldRegister = false;
+ if (shouldRegisterAttributionSource()) {
+ shouldRegister = mParams.shouldRegisterAttributionSource();
+ }
+ return registerAttributionSourceIfNeeded(oldSource.withDeviceId(deviceId), shouldRegister);
+ }
+
+ private @NonNull AttributionSource registerAttributionSourceIfNeeded(
+ @NonNull AttributionSource attributionSource, boolean shouldRegister) {
+ if (shouldRegister || attributionSource.getNext() != null) {
+ return getSystemService(PermissionManager.class)
.registerAttributionSource(attributionSource);
}
return attributionSource;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d8f03b1..dfae48b 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -8725,6 +8725,12 @@
* <p>The messages should be added in chronologic order, i.e. the oldest first,
* the newest last.
*
+ * <p>Multiple Messages in a row with the same timestamp and sender may be grouped as a
+ * single message. This means an app should represent a message that has both an image and
+ * text as two Message objects, one with the image (and fallback text), and the other with
+ * the message text. For consistency, a text message (if any) should be provided after Uri
+ * content.
+ *
* @param message The {@link Message} to be displayed
* @return this object for method chaining
*/
@@ -9359,6 +9365,15 @@
}
}
+ /*
+ * An object representing a simple message or piece of media within a mixed-media message.
+ *
+ * This object can only represent text or a single binary piece of media. For apps which
+ * support mixed-media messages (e.g. text + image), multiple Messages should be used, one
+ * to represent each piece of the message, and they should all be given the same timestamp.
+ * For consistency, a text message should be added last of all Messages with the same
+ * timestamp.
+ */
public static final class Message {
/** @hide */
public static final String KEY_TEXT = "text";
@@ -9447,8 +9462,9 @@
/**
* Sets a binary blob of data and an associated MIME type for a message. In the case
- * where the platform doesn't support the MIME type, the original text provided in the
- * constructor will be used.
+ * where the platform or the UI state doesn't support the MIME type, the original text
+ * provided in the constructor will be used. When this data can be presented to the
+ * user, the original text will only be used as accessibility text.
* @param dataMimeType The MIME type of the content. See
* {@link android.graphics.ImageDecoder#isMimeTypeSupported(String)} for a list of
* supported image MIME types.
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 4512180..8aa2c35 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -36,6 +36,8 @@
import android.util.Slog;
import android.view.View;
+import com.android.internal.ravenwood.RavenwoodEnvironment;
+
import dalvik.system.VMRuntime;
import java.util.ArrayList;
@@ -49,6 +51,10 @@
*/
@RavenwoodKeepWholeClass
public class Build {
+ static {
+ // Set up the default system properties.
+ RavenwoodEnvironment.ensureRavenwoodInitialized();
+ }
private static final String TAG = "Build";
/** Value used for when a build property is unknown. */
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
index 1898407..b65e3eb 100644
--- a/core/java/android/view/DragEvent.java
+++ b/core/java/android/view/DragEvent.java
@@ -156,6 +156,13 @@
private float mOffsetX;
private float mOffsetY;
+ /**
+ * The View#DRAG_FLAG_* flags used to start the current drag, only provided if the target window
+ * has the {@link WindowManager.LayoutParams#PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP} flag
+ * and is only sent with {@link #ACTION_DRAG_STARTED} and {@link #ACTION_DROP}.
+ */
+ private int mFlags;
+
private DragEvent mNext;
private RuntimeException mRecycledLocation;
private boolean mRecycled;
@@ -290,7 +297,7 @@
private DragEvent() {
}
- private void init(int action, float x, float y, float offsetX, float offsetY,
+ private void init(int action, float x, float y, float offsetX, float offsetY, int flags,
ClipDescription description, ClipData data, SurfaceControl dragSurface,
IDragAndDropPermissions dragAndDropPermissions, Object localState, boolean result) {
mAction = action;
@@ -298,6 +305,7 @@
mY = y;
mOffsetX = offsetX;
mOffsetY = offsetY;
+ mFlags = flags;
mClipDescription = description;
mClipData = data;
mDragSurface = dragSurface;
@@ -307,19 +315,19 @@
}
static DragEvent obtain() {
- return DragEvent.obtain(0, 0f, 0f, 0f, 0f, null, null, null, null, null, false);
+ return DragEvent.obtain(0, 0f, 0f, 0f, 0f, 0, null, null, null, null, null, false);
}
/** @hide */
public static DragEvent obtain(int action, float x, float y, float offsetX, float offsetY,
- Object localState, ClipDescription description, ClipData data,
+ int flags, Object localState, ClipDescription description, ClipData data,
SurfaceControl dragSurface, IDragAndDropPermissions dragAndDropPermissions,
boolean result) {
final DragEvent ev;
synchronized (gRecyclerLock) {
if (gRecyclerTop == null) {
ev = new DragEvent();
- ev.init(action, x, y, offsetX, offsetY, description, data, dragSurface,
+ ev.init(action, x, y, offsetX, offsetY, flags, description, data, dragSurface,
dragAndDropPermissions, localState, result);
return ev;
}
@@ -331,7 +339,7 @@
ev.mRecycled = false;
ev.mNext = null;
- ev.init(action, x, y, offsetX, offsetY, description, data, dragSurface,
+ ev.init(action, x, y, offsetX, offsetY, flags, description, data, dragSurface,
dragAndDropPermissions, localState, result);
return ev;
@@ -341,8 +349,8 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static DragEvent obtain(DragEvent source) {
return obtain(source.mAction, source.mX, source.mY, source.mOffsetX, source.mOffsetY,
- source.mLocalState, source.mClipDescription, source.mClipData, source.mDragSurface,
- source.mDragAndDropPermissions, source.mDragResult);
+ source.mFlags, source.mLocalState, source.mClipDescription, source.mClipData,
+ source.mDragSurface, source.mDragAndDropPermissions, source.mDragResult);
}
/**
@@ -424,6 +432,11 @@
}
/** @hide */
+ public int getDragFlags() {
+ return mFlags;
+ }
+
+ /** @hide */
public IDragAndDropPermissions getDragAndDropPermissions() {
return mDragAndDropPermissions;
}
@@ -571,6 +584,7 @@
dest.writeFloat(mY);
dest.writeFloat(mOffsetX);
dest.writeFloat(mOffsetY);
+ dest.writeInt(mFlags);
dest.writeInt(mDragResult ? 1 : 0);
if (mClipData == null) {
dest.writeInt(0);
@@ -610,6 +624,7 @@
event.mY = in.readFloat();
event.mOffsetX = in.readFloat();
event.mOffsetY = in.readFloat();
+ event.mFlags = in.readInt();
event.mDragResult = (in.readInt() != 0);
if (in.readInt() != 0) {
event.mClipData = ClipData.CREATOR.createFromParcel(in);
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 3743035..f628c21 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -68,7 +68,8 @@
/**
* Called when this window retrieved control over a specified set of insets sources.
*/
- void insetsControlChanged(in InsetsState insetsState, in InsetsSourceControl[] activeControls);
+ void insetsControlChanged(in InsetsState insetsState,
+ in InsetsSourceControl.Array activeControls);
/**
* Called when a set of insets source window should be shown by policy.
diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
index 8c14de6..fc1852d 100644
--- a/core/java/android/view/ImeBackAnimationController.java
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -255,10 +255,11 @@
private boolean isBackAnimationAllowed() {
// back animation is allowed in all cases except when softInputMode is adjust_resize AND
- // there is no app-registered WindowInsetsAnimationCallback.
+ // there is no app-registered WindowInsetsAnimationCallback AND edge-to-edge is not enabled.
return (mViewRoot.mWindowAttributes.softInputMode & SOFT_INPUT_MASK_ADJUST)
!= SOFT_INPUT_ADJUST_RESIZE
- || (mViewRoot.mView != null && mViewRoot.mView.hasWindowInsetsAnimationCallback());
+ || (mViewRoot.mView != null && mViewRoot.mView.hasWindowInsetsAnimationCallback())
+ || mViewRoot.mAttachInfo.mContentOnApplyWindowInsetsListener == null;
}
private boolean isAdjustPan() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0715474..8e2b7f1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -10234,7 +10234,7 @@
// force DRAG_EXITED_EVENT if appropriate
DragEvent event = DragEvent.obtain(
isExiting ? DragEvent.ACTION_DRAG_EXITED : DragEvent.ACTION_DRAG_LOCATION,
- x, y, 0 /* offsetX */, 0 /* offsetY */, null/* localState */,
+ x, y, 0 /* offsetX */, 0 /* offsetY */, 0 /* flags */, null/* localState */,
null/* description */, null /* data */, null /* dragSurface */,
null /* dragAndDropPermissions */, false /* result */);
dispatchDragEvent(event);
@@ -11258,10 +11258,10 @@
@Override
public void insetsControlChanged(InsetsState insetsState,
- InsetsSourceControl[] activeControls) {
+ InsetsSourceControl.Array activeControls) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
- viewAncestor.dispatchInsetsControlChanged(insetsState, activeControls);
+ viewAncestor.dispatchInsetsControlChanged(insetsState, activeControls.get());
}
}
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 5cdcf0a..46b41ae 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -532,7 +532,7 @@
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
DEVICE_CONFIG_IGNORE_VIEW_STATE_RESET_TO_EMPTY,
- false);
+ true);
}
/** @hide */
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index 6fe0784..94d7811 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -33,7 +33,7 @@
*
* @hide
*/
-public class BackProgressAnimator {
+public class BackProgressAnimator implements DynamicAnimation.OnAnimationUpdateListener {
/**
* A factor to scale the input progress by, so that it works better with the spring.
* We divide the output progress by this value before sending it to apps, so that apps
@@ -43,6 +43,7 @@
private final SpringAnimation mSpring;
private ProgressCallback mCallback;
private float mProgress = 0;
+ private float mVelocity = 0;
private BackMotionEvent mLastBackEvent;
private boolean mBackAnimationInProgress = false;
@Nullable
@@ -67,7 +68,6 @@
@Override
public void setValue(BackProgressAnimator animator, float value) {
animator.setProgress(value);
- animator.updateProgressValue(value);
}
@Override
@@ -76,6 +76,11 @@
}
};
+ @Override
+ public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
+ updateProgressValue(value, velocity);
+ }
+
/** A callback to be invoked when there's a progress value update from the animator. */
public interface ProgressCallback {
@@ -85,6 +90,7 @@
public BackProgressAnimator() {
mSpring = new SpringAnimation(this, PROGRESS_PROP);
+ mSpring.addUpdateListener(this);
mSpring.setSpring(new SpringForce()
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
@@ -117,7 +123,7 @@
mLastBackEvent = event;
mCallback = callback;
mBackAnimationInProgress = true;
- updateProgressValue(0);
+ updateProgressValue(0, 0);
}
/**
@@ -126,7 +132,7 @@
public void reset() {
if (mBackCancelledFinishRunnable != null) {
// Ensure that last progress value that apps see is 0
- updateProgressValue(0);
+ updateProgressValue(0, 0);
invokeBackCancelledRunnable();
}
mSpring.animateToFinalPosition(0);
@@ -167,7 +173,15 @@
return mBackAnimationInProgress;
}
- private void updateProgressValue(float progress) {
+ /**
+ * @return The last recorded velocity. Unit: change in progress per second
+ */
+ public float getVelocity() {
+ return mVelocity / SCALE_FACTOR;
+ }
+
+ private void updateProgressValue(float progress, float velocity) {
+ mVelocity = velocity;
if (mLastBackEvent == null || mCallback == null || !mBackAnimationInProgress) {
return;
}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ef55cc0..5e0107e 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -140,4 +140,14 @@
metadata {
purpose: PURPOSE_FEATURE
}
+}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "insets_control_changed_item"
+ description: "Pass insetsControlChanged through ClientTransaction to fix the racing"
+ bug: "339380439"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
deleted file mode 100644
index f306b0b..0000000
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.pm.pkg.component;
-
-import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
-
-import android.aconfig.nano.Aconfig;
-import android.aconfig.nano.Aconfig.parsed_flag;
-import android.aconfig.nano.Aconfig.parsed_flags;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.res.Flags;
-import android.content.res.XmlResourceParser;
-import android.os.Environment;
-import android.os.Process;
-import android.util.ArrayMap;
-import android.util.Slog;
-import android.util.Xml;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.utils.TypedXmlPullParser;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A class that manages a cache of all device feature flags and their default + override values.
- * This class performs a very similar job to the one in {@code SettingsProvider}, with an important
- * difference: this is a part of system server and is available for the server startup. Package
- * parsing happens at the startup when {@code SettingsProvider} isn't available yet, so we need an
- * own copy of the code here.
- * @hide
- */
-public class AconfigFlags {
- private static final String LOG_TAG = "AconfigFlags";
-
- private static final List<String> sTextProtoFilesOnDevice = List.of(
- "/system/etc/aconfig_flags.pb",
- "/system_ext/etc/aconfig_flags.pb",
- "/product/etc/aconfig_flags.pb",
- "/vendor/etc/aconfig_flags.pb");
-
- private final ArrayMap<String, Boolean> mFlagValues = new ArrayMap<>();
-
- public AconfigFlags() {
- if (!Flags.manifestFlagging()) {
- Slog.v(LOG_TAG, "Feature disabled, skipped all loading");
- return;
- }
- for (String fileName : sTextProtoFilesOnDevice) {
- try (var inputStream = new FileInputStream(fileName)) {
- loadAconfigDefaultValues(inputStream.readAllBytes());
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Failed to read Aconfig values from " + fileName, e);
- }
- }
- if (Process.myUid() == Process.SYSTEM_UID) {
- // Server overrides are only accessible to the system, no need to even try loading them
- // in user processes.
- loadServerOverrides();
- }
- }
-
- private void loadServerOverrides() {
- // Reading the proto files is enough for READ_ONLY flags but if it's a READ_WRITE flag
- // (which you can check with `flag.getPermission() == flag_permission.READ_WRITE`) then we
- // also need to check if there is a value pushed from the server in the file
- // `/data/system/users/0/settings_config.xml`. It will be in a <setting> node under the
- // root <settings> node with "name" attribute == "flag_namespace/flag_package.flag_name".
- // The "value" attribute will be true or false.
- //
- // The "name" attribute could also be "<namespace>/flag_namespace?flag_package.flag_name"
- // (prefixed with "staged/" or "device_config_overrides/" and a different separator between
- // namespace and name). This happens when a flag value is overridden either with a pushed
- // one from the server, or from the local command.
- // When the device reboots during package parsing, the staged value will still be there and
- // only later it will become a regular/non-staged value after SettingsProvider is
- // initialized.
- //
- // In all cases, when there is more than one value, the priority is:
- // device_config_overrides > staged > default
- //
-
- final var settingsFile = new File(Environment.getUserSystemDirectory(0),
- "settings_config.xml");
- try (var inputStream = new FileInputStream(settingsFile)) {
- TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
- if (parser.next() != XmlPullParser.END_TAG && "settings".equals(parser.getName())) {
- final var flagPriority = new ArrayMap<String, Integer>();
- final int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- if (!"setting".equals(parser.getName())) {
- continue;
- }
- String name = parser.getAttributeValue(null, "name");
- final String value = parser.getAttributeValue(null, "value");
- if (name == null || value == null) {
- continue;
- }
- // A non-boolean setting is definitely not an Aconfig flag value.
- if (!"false".equalsIgnoreCase(value) && !"true".equalsIgnoreCase(value)) {
- continue;
- }
- final var overridePrefix = "device_config_overrides/";
- final var stagedPrefix = "staged/";
- String separator = "/";
- String prefix = "default";
- int priority = 0;
- if (name.startsWith(overridePrefix)) {
- prefix = overridePrefix;
- name = name.substring(overridePrefix.length());
- separator = ":";
- priority = 20;
- } else if (name.startsWith(stagedPrefix)) {
- prefix = stagedPrefix;
- name = name.substring(stagedPrefix.length());
- separator = "*";
- priority = 10;
- }
- final String flagPackageAndName = parseFlagPackageAndName(name, separator);
- if (flagPackageAndName == null) {
- continue;
- }
- // We ignore all settings that aren't for flags. We'll know they are for flags
- // if they correspond to flags read from the proto files.
- if (!mFlagValues.containsKey(flagPackageAndName)) {
- continue;
- }
- Slog.d(LOG_TAG, "Found " + prefix
- + " Aconfig flag value for " + flagPackageAndName + " = " + value);
- final Integer currentPriority = flagPriority.get(flagPackageAndName);
- if (currentPriority != null && currentPriority >= priority) {
- Slog.i(LOG_TAG, "Skipping " + prefix + " flag " + flagPackageAndName
- + " because of the existing one with priority " + currentPriority);
- continue;
- }
- flagPriority.put(flagPackageAndName, priority);
- mFlagValues.put(flagPackageAndName, Boolean.parseBoolean(value));
- }
- }
- } catch (IOException | XmlPullParserException e) {
- Slog.e(LOG_TAG, "Failed to read Aconfig values from settings_config.xml", e);
- }
- }
-
- private static String parseFlagPackageAndName(String fullName, String separator) {
- int index = fullName.indexOf(separator);
- if (index < 0) {
- return null;
- }
- return fullName.substring(index + 1);
- }
-
- private void loadAconfigDefaultValues(byte[] fileContents) throws IOException {
- parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents);
- for (parsed_flag flag : parsedFlags.parsedFlag) {
- String flagPackageAndName = flag.package_ + "." + flag.name;
- boolean flagValue = (flag.state == Aconfig.ENABLED);
- Slog.v(LOG_TAG, "Read Aconfig default flag value "
- + flagPackageAndName + " = " + flagValue);
- mFlagValues.put(flagPackageAndName, flagValue);
- }
- }
-
- /**
- * Get the flag value, or null if the flag doesn't exist.
- * @param flagPackageAndName Full flag name formatted as 'package.flag'
- * @return the current value of the given Aconfig flag, or null if there is no such flag
- */
- @Nullable
- public Boolean getFlagValue(@NonNull String flagPackageAndName) {
- Boolean value = mFlagValues.get(flagPackageAndName);
- Slog.d(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
- return value;
- }
-
- /**
- * Check if the element in {@code parser} should be skipped because of the feature flag.
- * @param parser XML parser object currently parsing an element
- * @return true if the element is disabled because of its feature flag
- */
- public boolean skipCurrentElement(@NonNull XmlResourceParser parser) {
- if (!Flags.manifestFlagging()) {
- return false;
- }
- String featureFlag = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "featureFlag");
- if (featureFlag == null) {
- return false;
- }
- featureFlag = featureFlag.strip();
- boolean negated = false;
- if (featureFlag.startsWith("!")) {
- negated = true;
- featureFlag = featureFlag.substring(1).strip();
- }
- final Boolean flagValue = getFlagValue(featureFlag);
- if (flagValue == null) {
- Slog.w(LOG_TAG, "Skipping element " + parser.getName()
- + " due to unknown feature flag " + featureFlag);
- return true;
- }
- // Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated)
- if (flagValue == negated) {
- Slog.v(LOG_TAG, "Skipping element " + parser.getName()
- + " behind feature flag " + featureFlag + " = " + flagValue);
- return true;
- }
- return false;
- }
-
- /**
- * Add Aconfig flag values for testing flagging of manifest entries.
- * @param flagValues A map of flag name -> value.
- */
- @VisibleForTesting
- public void addFlagValuesForTesting(@NonNull Map<String, Boolean> flagValues) {
- mFlagValues.putAll(flagValues);
- }
-}
diff --git a/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
index 8858f94..db08005 100644
--- a/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
@@ -61,9 +61,6 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
- continue;
- }
final ParseResult result;
if ("meta-data".equals(parser.getName())) {
diff --git a/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
index bb01581..0b04591 100644
--- a/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
+++ b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
@@ -27,7 +27,6 @@
import com.android.internal.R;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
-import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -81,9 +80,6 @@
}
return input.success(prefixes);
} else if (type == XmlPullParser.START_TAG) {
- if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
- continue;
- }
if (parser.getName().equals(TAG_FINGERPRINT_PREFIX)) {
ParseResult<String> parsedPrefix =
readFingerprintPrefixValue(input, res, parser);
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
index 55baa53..9f71d88 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
@@ -393,9 +393,6 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
- continue;
- }
final ParseResult result;
if (parser.getName().equals("intent-filter")) {
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
index da48b23..05728ee 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
@@ -99,9 +99,6 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
- continue;
- }
final ParseResult result;
String nodeName = parser.getName();
@@ -200,9 +197,6 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
- continue;
- }
final ParseResult result;
String nodeName = parser.getName();
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
index 6af2a29..12aff1c 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
@@ -36,7 +36,6 @@
import com.android.internal.R;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
-import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.pm.pkg.parsing.ParsingUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -174,9 +173,6 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
- continue;
- }
String name = parser.getName();
final ParseResult result;
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
index c68ea2d..4ac542f8 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
@@ -34,7 +34,6 @@
import com.android.internal.R;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
-import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.pm.pkg.parsing.ParsingUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -138,9 +137,6 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
- continue;
- }
final ParseResult parseResult;
switch (parser.getName()) {
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 44fedb1..1dcd893 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -90,7 +90,6 @@
import com.android.internal.os.ClassLoaderFactory;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.permission.CompatibilityPermissionInfo;
-import com.android.internal.pm.pkg.component.AconfigFlags;
import com.android.internal.pm.pkg.component.ComponentMutateUtils;
import com.android.internal.pm.pkg.component.ComponentParseUtils;
import com.android.internal.pm.pkg.component.InstallConstraintsTagParser;
@@ -293,7 +292,6 @@
@NonNull
private final List<PermissionManager.SplitPermissionInfo> mSplitPermissionInfos;
private final Callback mCallback;
- private static final AconfigFlags sAconfigFlags = new AconfigFlags();
public ParsingPackageUtils(String[] separateProcesses, DisplayMetrics displayMetrics,
@NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
@@ -763,9 +761,6 @@
if (outerDepth + 1 < parser.getDepth() || type != XmlPullParser.START_TAG) {
continue;
}
- if (sAconfigFlags.skipCurrentElement(parser)) {
- continue;
- }
final ParseResult result;
String tagName = parser.getName();
@@ -842,9 +837,6 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (sAconfigFlags.skipCurrentElement(parser)) {
- continue;
- }
ParsedMainComponent mainComponent = null;
@@ -988,9 +980,6 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (sAconfigFlags.skipCurrentElement(parser)) {
- continue;
- }
String tagName = parser.getName();
final ParseResult result;
@@ -1610,9 +1599,6 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (sAconfigFlags.skipCurrentElement(parser)) {
- continue;
- }
final String innerTagName = parser.getName();
if (innerTagName.equals("uses-feature")) {
@@ -1853,9 +1839,6 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (sAconfigFlags.skipCurrentElement(parser)) {
- continue;
- }
if (parser.getName().equals("intent")) {
ParseResult<ParsedIntentInfoImpl> result = ParsedIntentInfoUtils.parseIntentInfo(
null /*className*/, pkg, res, parser, true /*allowGlobs*/,
@@ -2202,9 +2185,6 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (sAconfigFlags.skipCurrentElement(parser)) {
- continue;
- }
final ParseResult result;
String tagName = parser.getName();
@@ -2793,9 +2773,6 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (sAconfigFlags.skipCurrentElement(parser)) {
- continue;
- }
final String nodeName = parser.getName();
if (nodeName.equals("additional-certificate")) {
@@ -3481,11 +3458,4 @@
@NonNull Set<String> getInstallConstraintsAllowlist();
}
-
- /**
- * Getter for the flags object
- */
- public static AconfigFlags getAconfigFlags() {
- return sAconfigFlags;
- }
}
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
index 4a3dfbe..1494425 100644
--- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
+++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
@@ -15,14 +15,23 @@
*/
package com.android.internal.ravenwood;
+import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass;
+
/**
* Class to interact with the Ravenwood environment.
*/
@android.ravenwood.annotation.RavenwoodKeepWholeClass
-public class RavenwoodEnvironment {
+@RavenwoodNativeSubstitutionClass(
+ "com.android.platform.test.ravenwood.nativesubstitution.RavenwoodEnvironment_host")
+public final class RavenwoodEnvironment {
+ public static final String TAG = "RavenwoodEnvironment";
+
private static RavenwoodEnvironment sInstance = new RavenwoodEnvironment();
private RavenwoodEnvironment() {
+ if (isRunningOnRavenwood()) {
+ ensureRavenwoodInitializedInternal();
+ }
}
/**
@@ -33,6 +42,21 @@
}
/**
+ * Initialize the ravenwood environment if it hasn't happened already, if running on Ravenwood.
+ *
+ * No-op if called on the device side.
+ */
+ public static void ensureRavenwoodInitialized() {
+ }
+
+ private static void ensureRavenwoodInitialized$ravenwood() {
+ getInstance(); // This is enough to initialize the environment.
+ }
+
+ /** Initialize the ravenwood environment */
+ private static native void ensureRavenwoodInitializedInternal();
+
+ /**
* USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment.
*
* <p>Using this allows code to behave differently on a real device and on Ravenwood, but
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 3fc4fff..7f896ff 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -64,7 +64,7 @@
@Override
public void insetsControlChanged(InsetsState insetsState,
- InsetsSourceControl[] activeControls) {
+ InsetsSourceControl.Array activeControls) {
}
@Override
diff --git a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
index 5917cc1..58e5be2 100644
--- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
+++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
@@ -99,6 +99,8 @@
} catch (WindowManager.BadTokenException e) {
// activity isn't running, we will ignore BadTokenException.
}
+ mViewRoot.setOnContentApplyWindowInsetsListener(
+ mock(Window.OnContentApplyWindowInsetsListener.class));
mBackAnimationController = new ImeBackAnimationController(mViewRoot, mInsetsController);
when(mWindowInsetsAnimationController.getHiddenStateInsets()).thenReturn(Insets.NONE);
@@ -132,6 +134,19 @@
}
@Test
+ public void testAdjustResizeWithEdgeToEdgePlaysAnim() {
+ // set OnContentApplyWindowInsetsListener to null (to simulate edge-to-edge enabled) and
+ // softInputMode=adjustResize
+ mViewRoot.mWindowAttributes.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
+ mViewRoot.setOnContentApplyWindowInsetsListener(null);
+ // start back gesture
+ mBackAnimationController.onBackStarted(new BackEvent(0f, 0f, 0f, EDGE_LEFT));
+ // verify that ImeBackAnimationController takes control over IME insets
+ verify(mInsetsController, times(1)).controlWindowInsetsAnimation(anyInt(), any(), any(),
+ anyBoolean(), anyLong(), any(), anyInt(), anyBoolean());
+ }
+
+ @Test
public void testAdjustResizeWithoutAppWindowInsetsListenerNotPlayingAnim() {
// setup ViewRoot with softInputMode=adjustResize
mViewRoot.mWindowAttributes.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index c988c2f..71fc8c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -41,6 +41,9 @@
import android.window.BackNavigationInfo
import android.window.BackProgressAnimator
import android.window.IOnBackInvokedCallback
+import com.android.internal.dynamicanimation.animation.FloatValueHolder
+import com.android.internal.dynamicanimation.animation.SpringAnimation
+import com.android.internal.dynamicanimation.animation.SpringForce
import com.android.internal.jank.Cuj
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.internal.protolog.common.ProtoLog
@@ -70,6 +73,7 @@
protected val backAnimRect = Rect()
private val cropRect = Rect()
+ private val tempRectF = RectF()
private var cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
@@ -98,6 +102,12 @@
private var rightLetterboxLayer: SurfaceControl? = null
private var letterboxColor: Int = 0
+ private val postCommitFlingScale = FloatValueHolder(SPRING_SCALE)
+ private var lastPostCommitFlingScale = SPRING_SCALE
+ private val postCommitFlingSpring = SpringForce(SPRING_SCALE)
+ .setStiffness(SpringForce.STIFFNESS_LOW)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+
/** Background color to be used during the animation, also see [getBackgroundColor] */
protected var customizedBackgroundColor = 0
@@ -231,7 +241,7 @@
return deltaY
}
- protected open fun onGestureCommitted() {
+ protected open fun onGestureCommitted(velocity: Float) {
if (
closingTarget?.leash == null ||
enteringTarget?.leash == null ||
@@ -242,6 +252,14 @@
return
}
+ // kick off spring animation with the current velocity from the pre-commit phase, this
+ // affects the scaling of the closing activity during post-commit
+ val flingAnimation = SpringAnimation(postCommitFlingScale, SPRING_SCALE)
+ .setStartVelocity(min(0f, -velocity * SPRING_SCALE))
+ .setStartValue(SPRING_SCALE)
+ .setSpring(postCommitFlingSpring)
+ flingAnimation.start()
+
val valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(POST_COMMIT_DURATION)
valueAnimator.addUpdateListener { animation: ValueAnimator ->
val progress = animation.animatedFraction
@@ -291,6 +309,7 @@
removeLetterbox()
isLetterboxed = false
enteringHasSameLetterbox = false
+ lastPostCommitFlingScale = SPRING_SCALE
}
protected fun applyTransform(
@@ -300,7 +319,15 @@
baseTransformation: Transformation? = null
) {
if (leash == null || !leash.isValid) return
- val scale = rect.width() / backAnimRect.width()
+ tempRectF.set(rect)
+ if (leash == closingTarget?.leash) {
+ lastPostCommitFlingScale = (postCommitFlingScale.value / SPRING_SCALE).coerceIn(
+ minimumValue = MAX_FLING_SCALE, maximumValue = lastPostCommitFlingScale
+ )
+ // apply an additional scale to the closing target to account for fling velocity
+ tempRectF.scaleCentered(lastPostCommitFlingScale)
+ }
+ val scale = tempRectF.width() / backAnimRect.width()
val matrix = baseTransformation?.matrix ?: transformMatrix.apply { reset() }
val scalePivotX =
if (isLetterboxed && enteringHasSameLetterbox) {
@@ -309,7 +336,7 @@
0f
}
matrix.postScale(scale, scale, scalePivotX, 0f)
- matrix.postTranslate(rect.left, rect.top)
+ matrix.postTranslate(tempRectF.left, tempRectF.top)
transaction
.setAlpha(leash, keepMinimumAlpha(alpha))
.setMatrix(leash, matrix, tmpFloat9)
@@ -461,7 +488,7 @@
override fun onBackInvoked() {
progressAnimator.reset()
- onGestureCommitted()
+ onGestureCommitted(progressAnimator.velocity)
}
}
@@ -497,6 +524,8 @@
private const val MAX_SCRIM_ALPHA_DARK = 0.8f
private const val MAX_SCRIM_ALPHA_LIGHT = 0.2f
private const val POST_COMMIT_DURATION = 300L
+ private const val SPRING_SCALE = 100f
+ private const val MAX_FLING_SCALE = 0.6f
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
index f33c5b9..d6c5349 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
@@ -56,7 +56,7 @@
targetEnteringRect.scaleCentered(MAX_SCALE)
}
- override fun onGestureCommitted() {
+ override fun onGestureCommitted(velocity: Float) {
// We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
// coordinate of the gesture driven phase. Let's update the start and target rects and kick
// off the animator in the superclass
@@ -65,7 +65,7 @@
targetEnteringRect.set(backAnimRect)
targetClosingRect.set(backAnimRect)
targetClosingRect.offset(currentClosingRect.left + enteringStartOffset, 0f)
- super.onGestureCommitted()
+ super.onGestureCommitted(velocity)
}
override fun onPostCommitProgress(linearProgress: Float) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index da414cc..ef33b38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -353,7 +353,7 @@
@Override
public void insetsControlChanged(InsetsState insetsState,
- InsetsSourceControl[] activeControls) {}
+ InsetsSourceControl.Array activeControls) {}
@Override
public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 165feec..7e70d6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -316,7 +316,8 @@
}
// TODO(b/290391688): Also update the session data with task stack changes
pd.dragSession = new DragSession(ActivityTaskManager.getInstance(),
- mDisplayController.getDisplayLayout(displayId), event.getClipData());
+ mDisplayController.getDisplayLayout(displayId), event.getClipData(),
+ event.getDragFlags());
pd.dragSession.update();
pd.activeDragCount++;
pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index 8f1bc59..0addd43 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -40,6 +40,7 @@
public class DragSession {
private final ActivityTaskManager mActivityTaskManager;
private final ClipData mInitialDragData;
+ private final int mInitialDragFlags;
final DisplayLayout displayLayout;
// The activity info associated with the activity in the appData or the launchableIntent
@@ -62,9 +63,10 @@
boolean dragItemSupportsSplitscreen;
DragSession(ActivityTaskManager activityTaskManager,
- DisplayLayout dispLayout, ClipData data) {
+ DisplayLayout dispLayout, ClipData data, int dragFlags) {
mActivityTaskManager = activityTaskManager;
mInitialDragData = data;
+ mInitialDragFlags = dragFlags;
displayLayout = dispLayout;
}
@@ -94,6 +96,6 @@
dragItemSupportsSplitscreen = activityInfo == null
|| ActivityInfo.isResizeableMode(activityInfo.resizeMode);
appData = mInitialDragData.getItemAt(0).getIntent();
- launchableIntent = DragUtils.getLaunchIntent(mInitialDragData);
+ launchableIntent = DragUtils.getLaunchIntent(mInitialDragData, mInitialDragFlags);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
index 24f8e18..e215870 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -24,6 +24,7 @@
import android.content.ClipData;
import android.content.ClipDescription;
import android.view.DragEvent;
+import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -67,14 +68,18 @@
*/
@Nullable
public static PendingIntent getLaunchIntent(@NonNull DragEvent dragEvent) {
- return getLaunchIntent(dragEvent.getClipData());
+ return getLaunchIntent(dragEvent.getClipData(), dragEvent.getDragFlags());
}
/**
* Returns a launchable intent in the given `ClipData` or `null` if there is none.
*/
@Nullable
- public static PendingIntent getLaunchIntent(@NonNull ClipData data) {
+ public static PendingIntent getLaunchIntent(@NonNull ClipData data, int dragFlags) {
+ if ((dragFlags & View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) == 0) {
+ // Disallow launching the intent if the app does not want to delegate it to the system
+ return null;
+ }
for (int i = 0; i < data.getItemCount(); i++) {
final ClipData.Item item = data.getItemAt(i);
if (item.getIntentSender() != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index a7829c9..3a266d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -402,6 +402,11 @@
}
}
+ /**
+ * Cleans up the recents transition. This should generally not be called directly
+ * to cancel a transition after it has started, instead callers should call one of
+ * the cancel() methods to ensure that Launcher is notified.
+ */
void cleanUp() {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.cleanup", mInstanceId);
@@ -438,7 +443,8 @@
if (mListener == null || mTransition == null) {
Slog.e(TAG, "Missing listener or transition, hasListener=" + (mListener != null) +
" hasTransition=" + (mTransition != null));
- cleanUp();
+ cancel("No listener (" + (mListener == null)
+ + ") or no transition (" + (mTransition == null) + ")");
return false;
}
// First see if this is a valid recents transition.
@@ -462,7 +468,7 @@
if (mRecentsTask == null && !hasPausingTasks) {
// Recents is already running apparently, so this is a no-op.
Slog.e(TAG, "Tried to start recents while it is already running.");
- cleanUp();
+ cancel("No recents task and no pausing tasks");
return false;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 5dd9d8a..6e72e8d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -66,6 +66,7 @@
import android.os.RemoteException;
import android.view.DisplayInfo;
import android.view.DragEvent;
+import android.view.View;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -115,6 +116,7 @@
private DragAndDropPolicy mPolicy;
private ClipData mActivityClipData;
+ private PendingIntent mLaunchableIntentPendingIntent;
private ClipData mLaunchableIntentClipData;
private ClipData mNonResizeableActivityClipData;
private ClipData mTaskClipData;
@@ -151,7 +153,10 @@
mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mSplitScreenStarter));
mActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
- mLaunchableIntentClipData = createIntentClipData();
+ mLaunchableIntentPendingIntent = mock(PendingIntent.class);
+ when(mLaunchableIntentPendingIntent.getCreatorUserHandle())
+ .thenReturn(android.os.Process.myUserHandle());
+ mLaunchableIntentClipData = createIntentClipData(mLaunchableIntentPendingIntent);
mNonResizeableActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
setClipDataResizeable(mNonResizeableActivityClipData, false);
mTaskClipData = createAppClipData(MIMETYPE_APPLICATION_TASK);
@@ -202,16 +207,13 @@
/**
* Creates an intent-based clip data that is by default resizeable.
*/
- private ClipData createIntentClipData() {
+ private ClipData createIntentClipData(PendingIntent intent) {
ClipDescription clipDescription = new ClipDescription("Intent",
new String[] { MIMETYPE_TEXT_INTENT });
- PendingIntent intent = mock(PendingIntent.class);
- when(intent.getCreatorUserHandle()).thenReturn(android.os.Process.myUserHandle());
ClipData.Item item = new ClipData.Item.Builder()
.setIntentSender(intent.getIntentSender())
.build();
ClipData data = new ClipData(clipDescription, item);
- when(DragUtils.getLaunchIntent((ClipData) any())).thenReturn(intent);
return data;
}
@@ -259,16 +261,22 @@
@Test
public void testDragIntentOverFullscreenHome_expectOnlyFullscreenTarget() {
+ when(DragUtils.getLaunchIntent((ClipData) any(), anyInt())).thenReturn(
+ mLaunchableIntentPendingIntent);
dragOverFullscreenHome_expectOnlyFullscreenTarget(mLaunchableIntentClipData);
}
@Test
public void testDragIntentOverFullscreenApp_expectSplitScreenTargets() {
+ when(DragUtils.getLaunchIntent((ClipData) any(), anyInt())).thenReturn(
+ mLaunchableIntentPendingIntent);
dragOverFullscreenApp_expectSplitScreenTargets(mLaunchableIntentClipData);
}
@Test
public void testDragIntentOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
+ when(DragUtils.getLaunchIntent((ClipData) any(), anyInt())).thenReturn(
+ mLaunchableIntentPendingIntent);
dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(mLaunchableIntentClipData);
}
@@ -276,7 +284,7 @@
doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mHomeTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
- mLandscapeDisplayLayout, data);
+ mLandscapeDisplayLayout, data, 0 /* dragFlags */);
dragSession.update();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
@@ -291,7 +299,7 @@
doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
- mLandscapeDisplayLayout, data);
+ mLandscapeDisplayLayout, data, 0 /* dragFlags */);
dragSession.update();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
@@ -311,7 +319,7 @@
doReturn(false).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
- mPortraitDisplayLayout, data);
+ mPortraitDisplayLayout, data, 0 /* dragFlags */);
dragSession.update();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
@@ -331,7 +339,7 @@
public void testTargetHitRects() {
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
- mLandscapeDisplayLayout, mActivityClipData);
+ mLandscapeDisplayLayout, mActivityClipData, 0 /* dragFlags */);
dragSession.update();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = mPolicy.getTargets(mInsets);
@@ -345,6 +353,11 @@
}
}
+ @Test
+ public void testDisallowLaunchIntentWithoutDelegationFlag() {
+ assertTrue(DragUtils.getLaunchIntent(mLaunchableIntentClipData, 0) == null);
+ }
+
private Target filterTargetByType(ArrayList<Target> targets, int type) {
for (Target t : targets) {
if (type == t.type) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
index e731b06..d410151 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
@@ -74,7 +74,7 @@
@Test
fun onUnhandledDrop_noListener_expectNotifyUnhandled() {
// Simulate an unhandled drop
- val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
+ val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, 0, null, null, null,
null, null, false)
val wmCallback = mock<IUnhandledDragCallback>()
mController.onUnhandledDrop(dropEvent, wmCallback)
@@ -98,7 +98,7 @@
// Simulate an unhandled drop
val dragSurface = mock<SurfaceControl>()
- val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
+ val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, 0, null, null, null,
dragSurface, null, false)
val wmCallback = mock<IUnhandledDragCallback>()
mController.onUnhandledDrop(dropEvent, wmCallback)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 36e396fb..3b84333 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -79,8 +79,6 @@
suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean
suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode)
-
- suspend fun isAffectedByMute(audioStream: AudioStream): Boolean
}
class AudioRepositoryImpl(
@@ -152,8 +150,9 @@
minVolume = getMinVolume(audioStream),
maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
volume = audioManager.getStreamVolume(audioStream.value),
+ isAffectedByMute = audioManager.isStreamAffectedByMute(audioStream.value),
isAffectedByRingerMode = audioManager.isStreamAffectedByRingerMode(audioStream.value),
- isMuted = audioManager.isStreamMute(audioStream.value)
+ isMuted = audioManager.isStreamMute(audioStream.value),
)
}
@@ -187,12 +186,6 @@
withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value }
}
- override suspend fun isAffectedByMute(audioStream: AudioStream): Boolean {
- return withContext(backgroundCoroutineContext) {
- audioManager.isStreamAffectedByMute(audioStream.value)
- }
- }
-
private fun getMinVolume(stream: AudioStream): Int =
try {
audioManager.getStreamMinVolume(stream.value)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 0e5ebda..202ff40 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -50,11 +50,13 @@
suspend fun setVolume(audioStream: AudioStream, volume: Int) {
val streamModel = getAudioStream(audioStream).first()
val oldVolume = streamModel.volume
- audioRepository.setVolume(audioStream, volume)
- when {
- volume == streamModel.minVolume -> setMuted(audioStream, true)
- oldVolume == streamModel.minVolume && volume > streamModel.minVolume ->
- setMuted(audioStream, false)
+ if (volume != oldVolume) {
+ audioRepository.setVolume(audioStream, volume)
+ when {
+ volume == streamModel.minVolume -> setMuted(audioStream, true)
+ oldVolume == streamModel.minVolume && volume > streamModel.minVolume ->
+ setMuted(audioStream, false)
+ }
}
}
@@ -90,9 +92,6 @@
}
}
- suspend fun isAffectedByMute(audioStream: AudioStream): Boolean =
- audioRepository.isAffectedByMute(audioStream)
-
private suspend fun processVolume(
audioStreamModel: AudioStreamModel,
ringerMode: RingerMode,
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt
index c1be1ee..2c26af1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStreamModel.kt
@@ -22,6 +22,7 @@
val volume: Int,
val minVolume: Int,
val maxVolume: Int,
+ val isAffectedByMute: Boolean,
val isAffectedByRingerMode: Boolean,
val isMuted: Boolean,
)
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 9860cd8..8700680 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -173,6 +173,7 @@
volume = 50,
minVolume = MIN_VOLUME,
maxVolume = MAX_VOLUME,
+ isAffectedByMute = false,
isAffectedByRingerMode = false,
isMuted = false,
)
@@ -201,6 +202,7 @@
volume = 0,
minVolume = MIN_VOLUME,
maxVolume = MAX_VOLUME,
+ isAffectedByMute = false,
isAffectedByRingerMode = false,
isMuted = true,
)
@@ -230,6 +232,7 @@
volume = 0,
minVolume = MIN_VOLUME,
maxVolume = MAX_VOLUME,
+ isAffectedByMute = false,
isAffectedByRingerMode = false,
isMuted = false,
)
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index f3e2272..a2d7b2f 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -977,6 +977,13 @@
}
flag {
+ name: "new_touchpad_gestures_tutorial"
+ namespace: "systemui"
+ description: "Enables new interactive tutorial for learning touchpad gestures"
+ bug: "309928033"
+}
+
+flag {
name: "register_wallpaper_notifier_background"
namespace: "systemui"
description: "Decide whether to register wallpaper change broadcast receiver on background executor."
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
index f5dc154..bd5b795a 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
@@ -19,11 +19,14 @@
import android.view.View
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.KeyguardViewConfigurator
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.qualifiers.KeyguardRootView
import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.composable.LockscreenContent
import com.android.systemui.keyguard.ui.composable.LockscreenScene
import com.android.systemui.keyguard.ui.composable.LockscreenSceneBlueprintModule
import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.scene.shared.model.Scene
import dagger.Binds
import dagger.Module
@@ -60,5 +63,14 @@
): Set<LockscreenSceneBlueprint> {
return blueprints
}
+
+ @Provides
+ fun providesLockscreenContent(
+ viewModel: LockscreenContentViewModel,
+ blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
+ clockInteractor: KeyguardClockInteractor,
+ ): LockscreenContent {
+ return LockscreenContent(viewModel, blueprints, clockInteractor)
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index ca4ff83..99f7d0f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -36,9 +36,7 @@
* This is separate from the [LockscreenScene] because it's meant to support usage of this UI from
* outside the scene container framework.
*/
-class LockscreenContent
-@Inject
-constructor(
+class LockscreenContent(
private val viewModel: LockscreenContentViewModel,
private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
private val clockInteractor: KeyguardClockInteractor,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
index d996d25..166aa70 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
@@ -54,12 +54,19 @@
@SuppressLint("InflateParams")
val view =
remember(context) {
- LayoutInflater.from(context)
- .inflate(
- R.layout.keyguard_status_bar,
- null,
- false,
- ) as KeyguardStatusBarView
+ (LayoutInflater.from(context)
+ .inflate(
+ R.layout.keyguard_status_bar,
+ null,
+ false,
+ ) as KeyguardStatusBarView)
+ .also {
+ it.layoutParams =
+ ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
}
val viewController =
remember(view) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index 1c675e3..e17a146 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -26,11 +26,14 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.composable.LockscreenContent
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneViewModel
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
+import dagger.Lazy
+import java.util.Optional
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
@@ -40,6 +43,7 @@
constructor(
viewModel: NotificationsShadeSceneViewModel,
private val overlayShadeViewModel: OverlayShadeViewModel,
+ private val lockscreenContent: Lazy<Optional<LockscreenContent>>,
) : ComposableScene {
override val key = Scenes.NotificationsShade
@@ -55,6 +59,7 @@
viewModel = overlayShadeViewModel,
modifier = modifier,
horizontalArrangement = Arrangement.Start,
+ lockscreenContent = lockscreenContent,
) {
Text(
text = "Notifications list",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index 636c6c3..04f76f5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -26,13 +26,16 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.composable.LockscreenContent
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
+import java.util.Optional
@SysUISingleton
class QuickSettingsShadeScene
@@ -40,6 +43,7 @@
constructor(
viewModel: QuickSettingsShadeSceneViewModel,
private val overlayShadeViewModel: OverlayShadeViewModel,
+ private val lockscreenContent: Lazy<Optional<LockscreenContent>>,
) : ComposableScene {
override val key = Scenes.QuickSettingsShade
@@ -55,6 +59,7 @@
viewModel = overlayShadeViewModel,
modifier = modifier,
horizontalArrangement = Arrangement.End,
+ lockscreenContent = lockscreenContent,
) {
Text(
text = "Quick settings grid",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 00ef11d..736d805 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -27,11 +27,9 @@
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
@@ -40,14 +38,19 @@
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenContent
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
+import com.android.systemui.util.kotlin.getOrNull
+import dagger.Lazy
+import java.util.Optional
/** The overlay shade renders a lightweight shade UI container on top of a background scene. */
@Composable
fun SceneScope.OverlayShade(
viewModel: OverlayShadeViewModel,
horizontalArrangement: Arrangement.Horizontal,
+ lockscreenContent: Lazy<Optional<LockscreenContent>>,
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
@@ -55,7 +58,10 @@
Box(modifier) {
if (backgroundScene == Scenes.Lockscreen) {
- Lockscreen()
+ // Lockscreen content is optionally injected, because variants of System UI without a
+ // lockscreen cannot provide it.
+ val lockscreenContentOrNull = lockscreenContent.get().getOrNull()
+ lockscreenContentOrNull?.apply { Content(Modifier.fillMaxSize()) }
}
Scrim(onClicked = viewModel::onScrimClicked)
@@ -70,16 +76,6 @@
}
@Composable
-private fun Lockscreen(
- modifier: Modifier = Modifier,
-) {
- // TODO(b/338025605): This is a placeholder, replace with the actual lockscreen.
- Box(modifier = modifier.fillMaxSize().background(Color.LightGray)) {
- Text(text = "Lockscreen", modifier = Modifier.align(Alignment.Center))
- }
-}
-
-@Composable
private fun SceneScope.Scrim(
onClicked: () -> Unit,
modifier: Modifier = Modifier,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 18baee9..ad3d4ad8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -342,10 +342,8 @@
if (transition != previousTransition && transition != null && previousTransition != null) {
// The previous transition was interrupted by another transition.
- prepareInterruption(element)
- }
-
- if (transition == null && previousTransition != null) {
+ prepareInterruption(element, transition, previousTransition)
+ } else if (transition == null && previousTransition != null) {
// The transition was just finished.
element.sceneStates.values.forEach {
it.clearValuesBeforeInterruption()
@@ -356,46 +354,103 @@
return transition
}
-private fun prepareInterruption(element: Element) {
- // We look for the last unique state of this element so that we animate the delta with its
- // future state.
- val sceneStates = element.sceneStates.values
- var lastUniqueState: Element.SceneState? = null
- for (sceneState in sceneStates) {
- val offset = sceneState.lastOffset
+private fun prepareInterruption(
+ element: Element,
+ transition: TransitionState.Transition,
+ previousTransition: TransitionState.Transition,
+) {
+ val previousUniqueState = reconcileStates(element, previousTransition)
+ if (previousUniqueState == null) {
+ reconcileStates(element, transition)
+ return
+ }
- // If the element was placed in this scene...
- if (offset != Offset.Unspecified) {
- // ... and it is the first (and potentially the only) scene where the element was
- // placed, save the state for later.
- if (lastUniqueState == null) {
- lastUniqueState = sceneState
- } else {
- // The element was placed in multiple scenes: we abort the interruption for this
- // element.
- // TODO(b/290930950): Better support cases where a shared element animation is
- // disabled and the same element is drawn/placed in multiple scenes at the same
- // time.
- lastUniqueState = null
- break
+ val fromSceneState = element.sceneStates[transition.fromScene]
+ val toSceneState = element.sceneStates[transition.toScene]
+
+ if (
+ fromSceneState == null ||
+ toSceneState == null ||
+ sharedElementTransformation(element.key, transition)?.enabled != false
+ ) {
+ // If there is only one copy of the element or if the element is shared, animate deltas in
+ // both scenes.
+ fromSceneState?.updateValuesBeforeInterruption(previousUniqueState)
+ toSceneState?.updateValuesBeforeInterruption(previousUniqueState)
+ }
+}
+
+/**
+ * Reconcile the state of [element] in the fromScene and toScene of [transition] so that the values
+ * before interruption have their expected values, taking shared transitions into account.
+ *
+ * If the element had a unique state, i.e. it is shared in [transition] or it is only present in one
+ * of the scenes, return it.
+ */
+private fun reconcileStates(
+ element: Element,
+ transition: TransitionState.Transition,
+): Element.SceneState? {
+ val fromSceneState = element.sceneStates[transition.fromScene]
+ val toSceneState = element.sceneStates[transition.toScene]
+ when {
+ // Element is in both scenes.
+ fromSceneState != null && toSceneState != null -> {
+ val isSharedTransformationDisabled =
+ sharedElementTransformation(element.key, transition)?.enabled == false
+ when {
+ // Element shared transition is disabled so the element is placed in both scenes.
+ isSharedTransformationDisabled -> {
+ fromSceneState.updateValuesBeforeInterruption(fromSceneState)
+ toSceneState.updateValuesBeforeInterruption(toSceneState)
+ return null
+ }
+
+ // Element is shared and placed in fromScene only.
+ fromSceneState.lastOffset != Offset.Unspecified -> {
+ fromSceneState.updateValuesBeforeInterruption(fromSceneState)
+ toSceneState.updateValuesBeforeInterruption(fromSceneState)
+ return fromSceneState
+ }
+
+ // Element is shared and placed in toScene only.
+ toSceneState.lastOffset != Offset.Unspecified -> {
+ fromSceneState.updateValuesBeforeInterruption(toSceneState)
+ toSceneState.updateValuesBeforeInterruption(toSceneState)
+ return toSceneState
+ }
+
+ // Element is in none of the scenes.
+ else -> {
+ fromSceneState.updateValuesBeforeInterruption(null)
+ toSceneState.updateValuesBeforeInterruption(null)
+ return null
+ }
}
}
+
+ // Element is only in fromScene.
+ fromSceneState != null -> {
+ fromSceneState.updateValuesBeforeInterruption(fromSceneState)
+ return fromSceneState
+ }
+
+ // Element is only in toScene.
+ toSceneState != null -> {
+ toSceneState.updateValuesBeforeInterruption(toSceneState)
+ return toSceneState
+ }
+ else -> return null
}
+}
- val lastOffset = lastUniqueState?.lastOffset ?: Offset.Unspecified
- val lastSize = lastUniqueState?.lastSize ?: Element.SizeUnspecified
- val lastScale = lastUniqueState?.lastScale ?: Scale.Unspecified
- val lastAlpha = lastUniqueState?.lastAlpha ?: Element.AlphaUnspecified
+private fun Element.SceneState.updateValuesBeforeInterruption(lastState: Element.SceneState?) {
+ offsetBeforeInterruption = lastState?.lastOffset ?: Offset.Unspecified
+ sizeBeforeInterruption = lastState?.lastSize ?: Element.SizeUnspecified
+ scaleBeforeInterruption = lastState?.lastScale ?: Scale.Unspecified
+ alphaBeforeInterruption = lastState?.lastAlpha ?: Element.AlphaUnspecified
- // Store the state of the element before the interruption and reset the deltas.
- sceneStates.forEach { sceneState ->
- sceneState.offsetBeforeInterruption = lastOffset
- sceneState.sizeBeforeInterruption = lastSize
- sceneState.scaleBeforeInterruption = lastScale
- sceneState.alphaBeforeInterruption = lastAlpha
-
- sceneState.clearInterruptionDeltas()
- }
+ clearInterruptionDeltas()
}
private fun Element.SceneState.clearInterruptionDeltas() {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index 92d5c26..f1b2249 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -73,7 +73,16 @@
* the transition completes/settles.
*/
val isUserInputOngoing: Flow<Boolean>,
- ) : ObservableTransitionState
+ ) : ObservableTransitionState {
+ override fun toString(): String =
+ """Transition
+ |(from=$fromScene,
+ | to=$toScene,
+ | isInitiatedByUserInput=$isInitiatedByUserInput,
+ | isUserInputOngoing=$isUserInputOngoing
+ |)"""
+ .trimMargin()
+ }
fun isIdle(scene: SceneKey?): Boolean {
return this is Idle && (scene == null || this.currentScene == scene)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 6e114e3..3eef1f0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -1225,4 +1225,110 @@
assertThat(stateInC.scaleInterruptionDelta).isEqualTo(Scale.Zero)
assertThat(stateInC.alphaInterruptionDelta).isEqualTo(0f)
}
+
+ @Test
+ fun interruption_sharedTransitionDisabled() = runTest {
+ // 4 frames of animation.
+ val duration = 4 * 16
+ val layoutSize = DpSize(200.dp, 100.dp)
+ val fooSize = 100.dp
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutStateImpl(
+ SceneA,
+ transitions {
+ from(SceneA, to = SceneB) { spec = tween(duration, easing = LinearEasing) }
+
+ // Disable the shared transition during B => C.
+ from(SceneB, to = SceneC) {
+ spec = tween(duration, easing = LinearEasing)
+ sharedElement(TestElements.Foo, enabled = false)
+ }
+ },
+ )
+ }
+
+ @Composable
+ fun SceneScope.Foo(modifier: Modifier = Modifier) {
+ Box(modifier.element(TestElements.Foo).size(fooSize))
+ }
+
+ rule.setContent {
+ SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+ scene(SceneA) {
+ Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopStart)) }
+ }
+
+ scene(SceneB) {
+ Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopEnd)) }
+ }
+
+ scene(SceneC) {
+ Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.BottomEnd)) }
+ }
+ }
+ }
+
+ // The offset of Foo when idle in A, B or C.
+ val offsetInA = DpOffset.Zero
+ val offsetInB = DpOffset(layoutSize.width - fooSize, 0.dp)
+ val offsetInC = DpOffset(layoutSize.width - fooSize, layoutSize.height - fooSize)
+
+ // State is a transition A => B at 50% interrupted by B => C at 30%.
+ val aToB =
+ transition(from = SceneA, to = SceneB, progress = { 0.5f }, onFinish = neverFinish())
+ var bToCInterruptionProgress by mutableStateOf(1f)
+ val bToC =
+ transition(
+ from = SceneB,
+ to = SceneC,
+ progress = { 0.3f },
+ interruptionProgress = { bToCInterruptionProgress },
+ onFinish = neverFinish(),
+ )
+ rule.runOnUiThread { state.startTransition(aToB, transitionKey = null) }
+ rule.waitForIdle()
+ rule.runOnUiThread { state.startTransition(bToC, transitionKey = null) }
+
+ // Foo is placed in both B and C given that the shared transition is disabled. In B, its
+ // offset is impacted by the interruption but in C it is not.
+ val offsetInAToB = lerp(offsetInA, offsetInB, 0.5f)
+ val interruptionDelta = offsetInAToB - offsetInB
+ assertThat(interruptionDelta).isNotEqualTo(Offset.Zero)
+ rule
+ .onNode(isElement(TestElements.Foo, SceneB))
+ .assertPositionInRootIsEqualTo(
+ offsetInB.x + interruptionDelta.x,
+ offsetInB.y + interruptionDelta.y,
+ )
+
+ rule
+ .onNode(isElement(TestElements.Foo, SceneC))
+ .assertPositionInRootIsEqualTo(offsetInC.x, offsetInC.y)
+
+ // Manually finish A => B so only B => C is remaining.
+ bToCInterruptionProgress = 0f
+ rule.runOnUiThread { state.finishTransition(aToB, SceneB) }
+ rule
+ .onNode(isElement(TestElements.Foo, SceneB))
+ .assertPositionInRootIsEqualTo(offsetInB.x, offsetInB.y)
+ rule
+ .onNode(isElement(TestElements.Foo, SceneC))
+ .assertPositionInRootIsEqualTo(offsetInC.x, offsetInC.y)
+
+ // Interrupt B => C by B => A, starting directly at 70%
+ val bToA =
+ transition(
+ from = SceneB,
+ to = SceneA,
+ progress = { 0.7f },
+ interruptionProgress = { 1f },
+ )
+ rule.runOnUiThread { state.startTransition(bToA, transitionKey = null) }
+
+ // Foo should have the position it had in B right before the interruption.
+ rule
+ .onNode(isElement(TestElements.Foo, SceneB))
+ .assertPositionInRootIsEqualTo(offsetInB.x, offsetInB.y)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 4225291..6724851 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -138,10 +138,13 @@
toScene: SceneKey = TestScenes.SceneB,
): RecordedMotion {
val state =
- MutableSceneTransitionLayoutState(
- fromScene,
- transitions { from(fromScene, to = toScene, builder = transition) }
- )
+ toolkit.composeContentTestRule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ fromScene,
+ transitions { from(fromScene, to = toScene, builder = transition) }
+ )
+ }
+
return recordMotion(
content = { play ->
LaunchedEffect(play) {
diff --git a/packages/SystemUI/monet/Android.bp b/packages/SystemUI/monet/Android.bp
deleted file mode 100644
index c54fdab..0000000
--- a/packages/SystemUI/monet/Android.bp
+++ /dev/null
@@ -1,33 +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 {
- default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_library {
- name: "monet",
- platform_apis: true,
- static_libs: [
- "androidx.annotation_annotation",
- "androidx.core_core",
- "libmonet",
- ],
- srcs: [
- "src/**/*.java",
- "src/**/*.kt",
- ],
-}
diff --git a/packages/SystemUI/monet/AndroidManifest.xml b/packages/SystemUI/monet/AndroidManifest.xml
deleted file mode 100644
index 1fab528..0000000
--- a/packages/SystemUI/monet/AndroidManifest.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.systemui.monet">
-</manifest>
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
deleted file mode 100644
index 624f18d..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ /dev/null
@@ -1,372 +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.systemui.monet
-
-import android.annotation.ColorInt
-import android.app.WallpaperColors
-import android.graphics.Color
-import com.android.internal.graphics.ColorUtils
-import com.google.ux.material.libmonet.hct.Hct
-import com.google.ux.material.libmonet.scheme.DynamicScheme
-import com.google.ux.material.libmonet.scheme.SchemeContent
-import com.google.ux.material.libmonet.scheme.SchemeExpressive
-import com.google.ux.material.libmonet.scheme.SchemeFruitSalad
-import com.google.ux.material.libmonet.scheme.SchemeMonochrome
-import com.google.ux.material.libmonet.scheme.SchemeNeutral
-import com.google.ux.material.libmonet.scheme.SchemeRainbow
-import com.google.ux.material.libmonet.scheme.SchemeTonalSpot
-import com.google.ux.material.libmonet.scheme.SchemeVibrant
-import kotlin.math.absoluteValue
-import kotlin.math.roundToInt
-
-const val TAG = "ColorScheme"
-
-const val ACCENT1_CHROMA = 48.0f
-const val GOOGLE_BLUE = 0xFF1b6ef3.toInt()
-const val MIN_CHROMA = 5
-
-enum class Style{
- SPRITZ,
- TONAL_SPOT,
- VIBRANT,
- EXPRESSIVE,
- RAINBOW,
- FRUIT_SALAD,
- CONTENT,
- MONOCHROMATIC,
- CLOCK,
- CLOCK_VIBRANT
-}
-
-class TonalPalette
-internal constructor(
- private val materialTonalPalette: com.google.ux.material.libmonet.palettes.TonalPalette
-) {
- @Deprecated("Do not use. For color system only")
- val allShades: List<Int>
- val allShadesMapped: Map<Int, Int>
-
- init{
- allShades = SHADE_KEYS.map {key -> getAtTone(key.toFloat()) }
- allShadesMapped = SHADE_KEYS.zip(allShades).toMap()
- }
-
- // Dynamically computed tones across the full range from 0 to 1000
- fun getAtTone(shade: Float): Int = materialTonalPalette.tone(((1000.0f - shade) / 10f).toInt())
-
- // Predefined & precomputed tones
- val s0: Int
- get() = this.allShades[0]
- val s10: Int
- get() = this.allShades[1]
- val s50: Int
- get() = this.allShades[2]
- val s100: Int
- get() = this.allShades[3]
- val s200: Int
- get() = this.allShades[4]
- val s300: Int
- get() = this.allShades[5]
- val s400: Int
- get() = this.allShades[6]
- val s500: Int
- get() = this.allShades[7]
- val s600: Int
- get() = this.allShades[8]
- val s700: Int
- get() = this.allShades[9]
- val s800: Int
- get() = this.allShades[10]
- val s900: Int
- get() = this.allShades[11]
- val s1000: Int
- get() = this.allShades[12]
-
- companion object {
- val SHADE_KEYS = listOf(0, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
- }
-}
-
-@Deprecated("Please use com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors " +
- "instead")
-class ColorScheme(
- @ColorInt val seed: Int,
- val isDark: Boolean,
- val style: Style,
- val contrastLevel: Double
-) {
- var materialScheme: DynamicScheme
-
- private val proposedSeedHct: Hct = Hct.fromInt(seed)
- private val seedHct: Hct = Hct.fromInt(if (seed == Color.TRANSPARENT) {
- GOOGLE_BLUE
- } else if (style != Style.CONTENT && proposedSeedHct.chroma < 5) {
- GOOGLE_BLUE
- } else {
- seed
- })
-
- val accent1: TonalPalette
- val accent2: TonalPalette
- val accent3: TonalPalette
- val neutral1: TonalPalette
- val neutral2: TonalPalette
-
- constructor(@ColorInt seed: Int, darkTheme: Boolean) : this(seed, darkTheme, Style.TONAL_SPOT)
-
- @JvmOverloads
- constructor(
- @ColorInt seed: Int,
- darkTheme: Boolean,
- style: Style
- ) : this(seed, darkTheme, style, 0.0)
-
- @JvmOverloads
- constructor(
- wallpaperColors: WallpaperColors,
- darkTheme: Boolean,
- style: Style = Style.TONAL_SPOT
- ) : this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style)
-
- val backgroundColor
- get() = ColorUtils.setAlphaComponent(if (isDark) neutral1.s700 else neutral1.s10, 0xFF)
-
- val accentColor
- get() = ColorUtils.setAlphaComponent(if (isDark) accent1.s100 else accent1.s500, 0xFF)
-
- init {
- materialScheme = when (style) {
- Style.SPRITZ -> SchemeNeutral(seedHct, isDark, contrastLevel)
- Style.TONAL_SPOT -> SchemeTonalSpot(seedHct, isDark, contrastLevel)
- Style.VIBRANT -> SchemeVibrant(seedHct, isDark, contrastLevel)
- Style.EXPRESSIVE -> SchemeExpressive(seedHct, isDark, contrastLevel)
- Style.RAINBOW -> SchemeRainbow(seedHct, isDark, contrastLevel)
- Style.FRUIT_SALAD -> SchemeFruitSalad(seedHct, isDark, contrastLevel)
- Style.CONTENT -> SchemeContent(seedHct, isDark, contrastLevel)
- Style.MONOCHROMATIC -> SchemeMonochrome(seedHct, isDark, contrastLevel)
-
- // SystemUI Schemes
- Style.CLOCK -> SchemeClock(seedHct, isDark, contrastLevel)
- Style.CLOCK_VIBRANT -> SchemeClockVibrant(seedHct, isDark, contrastLevel)
- }
-
- accent1 = TonalPalette(materialScheme.primaryPalette)
- accent2 = TonalPalette(materialScheme.secondaryPalette)
- accent3 = TonalPalette(materialScheme.tertiaryPalette)
- neutral1 = TonalPalette(materialScheme.neutralPalette)
- neutral2 = TonalPalette(materialScheme.neutralVariantPalette)
- }
-
- val seedTone: Float
- get() = 1000f - proposedSeedHct.tone.toFloat() * 10f
-
- override fun toString(): String {
- return "ColorScheme {\n" +
- " seed color: ${stringForColor(seed)}\n" +
- " style: $style\n" +
- " palettes: \n" +
- " ${humanReadable("PRIMARY", accent1.allShades)}\n" +
- " ${humanReadable("SECONDARY", accent2.allShades)}\n" +
- " ${humanReadable("TERTIARY", accent3.allShades)}\n" +
- " ${humanReadable("NEUTRAL", neutral1.allShades)}\n" +
- " ${humanReadable("NEUTRAL VARIANT", neutral2.allShades)}\n" +
- "}"
- }
-
- companion object {
- /**
- * Identifies a color to create a color scheme from.
- *
- * @param wallpaperColors Colors extracted from an image via quantization.
- * @param filter If false, allow colors that have low chroma, creating grayscale themes.
- * @return ARGB int representing the color
- */
- @JvmStatic
- @JvmOverloads
- @ColorInt
- fun getSeedColor(wallpaperColors: WallpaperColors, filter: Boolean = true): Int {
- return getSeedColors(wallpaperColors, filter).first()
- }
-
- /**
- * Filters and ranks colors from WallpaperColors.
- *
- * @param wallpaperColors Colors extracted from an image via quantization.
- * @param filter If false, allow colors that have low chroma, creating grayscale themes.
- * @return List of ARGB ints, ordered from highest scoring to lowest.
- */
- @JvmStatic
- @JvmOverloads
- fun getSeedColors(wallpaperColors: WallpaperColors, filter: Boolean = true): List<Int> {
- val totalPopulation =
- wallpaperColors.allColors.values.reduce { a, b -> a + b }.toDouble()
- val totalPopulationMeaningless = (totalPopulation == 0.0)
- if (totalPopulationMeaningless) {
- // WallpaperColors with a population of 0 indicate the colors didn't come from
- // quantization. Instead of scoring, trust the ordering of the provided primary
- // secondary/tertiary colors.
- //
- // In this case, the colors are usually from a Live Wallpaper.
- val distinctColors =
- wallpaperColors.mainColors
- .map { it.toArgb() }
- .distinct()
- .filter {
- if (!filter) {
- true
- } else {
- Hct.fromInt(it).chroma >= MIN_CHROMA
- }
- }
- .toList()
- if (distinctColors.isEmpty()) {
- return listOf(GOOGLE_BLUE)
- }
- return distinctColors
- }
-
- val intToProportion =
- wallpaperColors.allColors.mapValues { it.value.toDouble() / totalPopulation }
- val intToHct = wallpaperColors.allColors.mapValues { Hct.fromInt(it.key) }
-
- // Get an array with 360 slots. A slot contains the percentage of colors with that hue.
- val hueProportions = huePopulations(intToHct, intToProportion, filter)
- // Map each color to the percentage of the image with its hue.
- val intToHueProportion =
- wallpaperColors.allColors.mapValues {
- val hct = intToHct[it.key]!!
- val hue = hct.hue.roundToInt()
- var proportion = 0.0
- for (i in hue - 15..hue + 15) {
- proportion += hueProportions[wrapDegrees(i)]
- }
- proportion
- }
- // Remove any inappropriate seed colors. For example, low chroma colors look grayscale
- // raising their chroma will turn them to a much louder color that may not have been
- // in the image.
- val filteredIntToHct =
- if (!filter) intToHct
- else
- (intToHct.filter {
- val hct = it.value
- val proportion = intToHueProportion[it.key]!!
- hct.chroma >= MIN_CHROMA &&
- (totalPopulationMeaningless || proportion > 0.01)
- })
- // Sort the colors by score, from high to low.
- val intToScoreIntermediate =
- filteredIntToHct.mapValues { score(it.value, intToHueProportion[it.key]!!) }
- val intToScore = intToScoreIntermediate.entries.toMutableList()
- intToScore.sortByDescending { it.value }
-
- // Go through the colors, from high score to low score.
- // If the color is distinct in hue from colors picked so far, pick the color.
- // Iteratively decrease the amount of hue distinctness required, thus ensuring we
- // maximize difference between colors.
- val minimumHueDistance = 15
- val seeds = mutableListOf<Int>()
- maximizeHueDistance@ for (i in 90 downTo minimumHueDistance step 1) {
- seeds.clear()
- for (entry in intToScore) {
- val int = entry.key
- val existingSeedNearby =
- seeds.find {
- val hueA = intToHct[int]!!.hue
- val hueB = intToHct[it]!!.hue
- hueDiff(hueA, hueB) < i
- } != null
- if (existingSeedNearby) {
- continue
- }
- seeds.add(int)
- if (seeds.size >= 4) {
- break@maximizeHueDistance
- }
- }
- }
-
- if (seeds.isEmpty()) {
- // Use gBlue 500 if there are 0 colors
- seeds.add(GOOGLE_BLUE)
- }
-
- return seeds
- }
-
- private fun wrapDegrees(degrees: Int): Int {
- return when {
- degrees < 0 -> {
- (degrees % 360) + 360
- }
- degrees >= 360 -> {
- degrees % 360
- }
- else -> {
- degrees
- }
- }
- }
-
- private fun hueDiff(a: Double, b: Double): Double {
- return 180f - ((a - b).absoluteValue - 180f).absoluteValue
- }
-
- private fun stringForColor(color: Int): String {
- val width = 4
- val hct = Hct.fromInt(color)
- val h = "H${hct.hue.roundToInt().toString().padEnd(width)}"
- val c = "C${hct.chroma.roundToInt().toString().padEnd(width)}"
- val t = "T${hct.tone.roundToInt().toString().padEnd(width)}"
- val hex = Integer.toHexString(color and 0xffffff).padStart(6, '0').uppercase()
- return "$h$c$t = #$hex"
- }
-
- private fun humanReadable(paletteName: String, colors: List<Int>): String {
- return "$paletteName\n" +
- colors.map { stringForColor(it) }.joinToString(separator = "\n") { it }
- }
-
- private fun score(hct: Hct, proportion: Double): Double {
- val proportionScore = 0.7 * 100.0 * proportion
- val chromaScore =
- if (hct.chroma < ACCENT1_CHROMA) 0.1 * (hct.chroma - ACCENT1_CHROMA)
- else 0.3 * (hct.chroma - ACCENT1_CHROMA)
- return chromaScore + proportionScore
- }
-
- private fun huePopulations(
- hctByColor: Map<Int, Hct>,
- populationByColor: Map<Int, Double>,
- filter: Boolean = true
- ): List<Double> {
- val huePopulation = List(size = 360, init = { 0.0 }).toMutableList()
-
- for (entry in populationByColor.entries) {
- val population = populationByColor[entry.key]!!
- val hct = hctByColor[entry.key]!!
- val hue = hct.hue.roundToInt() % 360
- if (filter && hct.chroma <= MIN_CHROMA) {
- continue
- }
- huePopulation[hue] = huePopulation[hue] + population
- }
-
- return huePopulation
- }
- }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClock.java b/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClock.java
deleted file mode 100644
index 4747cc5..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClock.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.monet;
-
-import static com.google.ux.material.libmonet.utils.MathUtils.clampDouble;
-
-import static java.lang.Double.max;
-
-import com.google.ux.material.libmonet.hct.Hct;
-import com.google.ux.material.libmonet.palettes.TonalPalette;
-import com.google.ux.material.libmonet.scheme.DynamicScheme;
-import com.google.ux.material.libmonet.scheme.Variant;
-
-public class SchemeClock extends DynamicScheme {
- public SchemeClock(Hct sourceColorHct, boolean isDark, double contrastLevel) {
- super(
- sourceColorHct,
- Variant.MONOCHROME,
- isDark,
- contrastLevel,
- /*primary*/
- TonalPalette.fromHueAndChroma(
- /*hue*/ sourceColorHct.getHue(),
- /*chroma*/ max(sourceColorHct.getChroma(), 20)
- ),
- /*secondary*/
- TonalPalette.fromHueAndChroma(
- /*hue*/ sourceColorHct.getHue() + 10.0,
- /*chroma*/ clampDouble(17, 40, sourceColorHct.getChroma() * 0.85)
- ),
- /*tertiary*/
- TonalPalette.fromHueAndChroma(
- /*hue*/ sourceColorHct.getHue() + 20.0,
- /*chroma*/ max(sourceColorHct.getChroma() + 20, 50)
- ),
-
- //not used
- TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
- TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0));
- }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClockVibrant.java b/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClockVibrant.java
deleted file mode 100644
index fb5e972..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClockVibrant.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.monet;
-
-import static java.lang.Double.max;
-
-import com.google.ux.material.libmonet.hct.Hct;
-import com.google.ux.material.libmonet.palettes.TonalPalette;
-import com.google.ux.material.libmonet.scheme.DynamicScheme;
-import com.google.ux.material.libmonet.scheme.Variant;
-
-public class SchemeClockVibrant extends DynamicScheme {
- public SchemeClockVibrant(Hct sourceColorHct, boolean isDark, double contrastLevel) {
- super(
- sourceColorHct,
- Variant.MONOCHROME,
- isDark,
- contrastLevel,
- /*primary*/
- TonalPalette.fromHueAndChroma(
- /*hue*/ sourceColorHct.getHue(),
- /*chroma*/ max(sourceColorHct.getChroma(), 70)
- ),
- /*secondary*/
- TonalPalette.fromHueAndChroma(
- /*hue*/ sourceColorHct.getHue() + 20.0,
- /*chroma*/ max(sourceColorHct.getChroma(), 70)
- ),
- /*tertiary*/
- TonalPalette.fromHueAndChroma(
- /*hue*/ sourceColorHct.getHue() + 60.0,
- /*chroma*/ max(sourceColorHct.getChroma(), 70)
- ),
-
- //not used
- TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
- TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0));
- }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java b/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java
deleted file mode 100644
index c8b9fe0..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java
+++ /dev/null
@@ -1,65 +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.systemui.monet;
-
-
-import androidx.annotation.ColorInt;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
-
-/**
- * Generate sets of colors that are shades of the same color
- */
-@VisibleForTesting
-public class Shades {
- /**
- * Combining the ability to convert between relative luminance and perceptual luminance with
- * contrast leads to a design system that can be based on a linear value to determine contrast,
- * rather than a ratio.
- *
- * This codebase implements a design system that has that property, and as a result, we can
- * guarantee that any shades 5 steps from each other have a contrast ratio of at least 4.5.
- * 4.5 is the requirement for smaller text contrast in WCAG 2.1 and earlier.
- *
- * However, lstar 50 does _not_ have a contrast ratio >= 4.5 with lstar 100.
- * lstar 49.6 is the smallest lstar that will lead to a contrast ratio >= 4.5 with lstar 100,
- * and it also contrasts >= 4.5 with lstar 100.
- */
- public static final float MIDDLE_LSTAR = 49.6f;
-
- /**
- * Generate shades of a color. Ordered in lightness _descending_.
- * <p>
- * The first shade will be at 95% lightness, the next at 90, 80, etc. through 0.
- *
- * @param hue hue in CAM16 color space
- * @param chroma chroma in CAM16 color space
- * @return shades of a color, as argb integers. Ordered by lightness descending.
- */
- public static @ColorInt int[] of(float hue, float chroma) {
- int[] shades = new int[12];
- // At tone 90 and above, blue and yellow hues can reach a much higher chroma.
- // To preserve a consistent appearance across all hues, use a maximum chroma of 40.
- shades[0] = ColorUtils.CAMToColor(hue, Math.min(40f, chroma), 99);
- shades[1] = ColorUtils.CAMToColor(hue, Math.min(40f, chroma), 95);
- for (int i = 2; i < 12; i++) {
- float lStar = (i == 6) ? MIDDLE_LSTAR : 100 - 10 * (i - 1);
- shades[i] = ColorUtils.CAMToColor(hue, chroma, lStar);
- }
- return shades;
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index 325a324..89c5495 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -195,6 +195,7 @@
}
@EnableFlags(FLAG_COMMUNAL_HUB)
+ @DisableFlags(FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT)
@Test
fun hubShowsKeyguardWidgetsByDefault() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 2fa94ef..229a711 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -126,6 +126,31 @@
}
@Test
+ fun changeScene_toGoneWhenTransitionToLockedFromGone() =
+ testScope.runTest {
+ underTest = kosmos.sceneInteractor
+ val currentScene by collectLastValue(underTest.currentScene)
+ val transitionTo by collectLastValue(underTest.transitioningTo)
+ kosmos.sceneContainerRepository.setTransitionState(
+ flowOf(
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.Gone,
+ toScene = Scenes.Lockscreen,
+ currentScene = flowOf(Scenes.Lockscreen),
+ progress = flowOf(.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ )
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(transitionTo).isEqualTo(Scenes.Lockscreen)
+
+ underTest.changeScene(Scenes.Gone, "simulate double tap power")
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
fun snapToScene_toUnknownScene_doesNothing() =
testScope.runTest {
val sceneKeys =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 677477d..ac66e66 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -49,6 +49,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.scene.domain.interactor.sceneContainerStartable
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -383,6 +384,43 @@
}
@Test
+ fun switchToGoneWhenDoubleTapPowerGestureIsTriggeredFromGone() =
+ testScope.runTest {
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+ val transitionStateFlow =
+ prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = true,
+ initialSceneKey = Scenes.Gone,
+ )
+ assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
+ underTest.start()
+
+ kosmos.fakePowerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_SLEEP,
+ lastSleepReason = WakeSleepReason.POWER_BUTTON,
+ powerButtonLaunchGestureTriggered = false,
+ )
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.Gone,
+ toScene = Scenes.Lockscreen,
+ currentScene = flowOf(Scenes.Lockscreen),
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ )
+ assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+
+ kosmos.fakePowerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_WAKE,
+ lastSleepReason = WakeSleepReason.POWER_BUTTON,
+ powerButtonLaunchGestureTriggered = true,
+ )
+ assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
fun hydrateSystemUiState() =
testScope.runTest {
val transitionStateFlow = prepareState()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index a163ca0..d620639 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -192,10 +192,12 @@
fun streamNotAffectedByMute_isNotMutable() {
with(kosmos) {
testScope.runTest {
- audioRepository.setIsAffectedByMute(audioStream, false)
- val isMutable = underTest.isAffectedByMute(audioStream)
+ val audioStreamModel by collectLastValue(underTest.getAudioStream(audioStream))
+ audioRepository.setAudioStreamModel(
+ audioStreamModel!!.copy(isAffectedByMute = false)
+ )
- assertThat(isMutable).isFalse()
+ assertThat(audioStreamModel!!.isAffectedByMute).isFalse()
}
}
}
@@ -230,6 +232,7 @@
testScope.runTest {
val audioStreamModel by
collectLastValue(audioRepository.getAudioStream(audioStream))
+ underTest.setVolume(audioStream, audioStreamModel!!.maxVolume)
runCurrent()
underTest.setVolume(audioStream, audioStreamModel!!.minVolume)
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 8813588..b8f71c1 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1261,6 +1261,7 @@
<item name="android:lineHeight">32sp</item>
<item name="android:gravity">center</item>
<item name="android:textAlignment">center</item>
+ <item name="android:hyphenationFrequency">full</item>
</style>
<style name="TextAppearance.Dialog.Body" parent="@android:style/TextAppearance.DeviceDefault.Medium">
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 3462164..93f3793 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -69,6 +69,7 @@
import com.android.systemui.inputmethod.InputMethodModule;
import com.android.systemui.keyboard.KeyboardModule;
import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule;
+import com.android.systemui.keyguard.ui.composable.LockscreenContent;
import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule;
import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule;
import com.android.systemui.log.dagger.LogModule;
@@ -364,6 +365,9 @@
@BindsOptionalOf
abstract FingerprintReEnrollNotification optionalFingerprintReEnrollNotification();
+ @BindsOptionalOf
+ abstract LockscreenContent optionalLockscreenContent();
+
@SysUISingleton
@Binds
abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index f9adc47..9cdba58 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -68,6 +68,7 @@
import android.window.TransitionInfo;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IKeyguardDrawnCallback;
import com.android.internal.policy.IKeyguardExitCallback;
@@ -308,6 +309,13 @@
private final WindowManagerOcclusionManager mWmOcclusionManager;
+ private final Lazy<FoldGracePeriodProvider> mFoldGracePeriodProvider = new Lazy<>() {
+ @Override
+ public FoldGracePeriodProvider get() {
+ return new FoldGracePeriodProvider();
+ }
+ };
+
@Inject
public KeyguardService(
KeyguardViewMediator keyguardViewMediator,
@@ -609,7 +617,8 @@
trace("showDismissibleKeyguard");
checkPermission();
mKeyguardViewMediator.showDismissibleKeyguard();
- if (SceneContainerFlag.isEnabled()) {
+
+ if (SceneContainerFlag.isEnabled() && mFoldGracePeriodProvider.get().isEnabled()) {
mSceneInteractorLazy.get().changeScene(
Scenes.Lockscreen, "KeyguardService.showDismissibleKeyguard");
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index bda5be4..fb1853f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -631,7 +631,7 @@
// color will need to use wallpaper's extracted color and consider if the
// wallpaper's color is dark or light.
val style = themeStyle ?: fetchThemeStyleFromSetting().also { themeStyle = it }
- val wallpaperColorScheme = ColorScheme(colors, darkTheme = false, style)
+ val wallpaperColorScheme = ColorScheme(colors, false, style)
val lightClockColor = wallpaperColorScheme.accent1.s100
val darkClockColor = wallpaperColorScheme.accent2.s600
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index c98f3b0..b33eaa2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -18,7 +18,6 @@
import android.content.res.Resources
import com.android.internal.annotations.VisibleForTesting
-import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -48,7 +47,7 @@
val longPress: KeyguardLongPressViewModel,
val shadeInteractor: ShadeInteractor,
@Application private val applicationScope: CoroutineScope,
- private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
+ unfoldTransitionInteractor: UnfoldTransitionInteractor,
) {
@VisibleForTesting val clockSize = clockInteractor.clockSize
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt
index 3b09f41..c97221e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt
@@ -107,7 +107,7 @@
return try {
// Set up media source app's logo.
val icon = applicationContext.packageManager.getApplicationIcon(packageName)
- ColorScheme(WallpaperColors.fromDrawable(icon), darkTheme = true, style)
+ ColorScheme(WallpaperColors.fromDrawable(icon), true, style)
} catch (e: PackageManager.NameNotFoundException) {
Log.w(tag, "Fail to get media app info", e)
null
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index a144dc2..c8e896d 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -84,7 +84,9 @@
SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it.scene != Scenes.Gone },
SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to
{
- it.scene == Scenes.NotificationsShade || it.scene == Scenes.Shade
+ it.scene == Scenes.Lockscreen ||
+ it.scene == Scenes.NotificationsShade ||
+ it.scene == Scenes.Shade
},
SYSUI_STATE_QUICK_SETTINGS_EXPANDED to
{
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index 2cc3985..d161c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -29,14 +29,17 @@
import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
+import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
import com.android.systemui.qs.panels.ui.compose.StretchedGridLayout
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoSet
+import javax.inject.Named
@Module
interface PanelsModule {
@@ -50,6 +53,8 @@
impl: NoopGridConsistencyInteractor
): GridTypeConsistencyInteractor
+ @Binds @Named("Default") fun bindDefaultGridLayout(impl: PartitionedGridLayout): GridLayout
+
companion object {
@Provides
@SysUISingleton
@@ -73,6 +78,14 @@
}
@Provides
+ @IntoSet
+ fun providePartitionedGridLayout(
+ gridLayout: PartitionedGridLayout
+ ): Pair<GridLayoutType, GridLayout> {
+ return Pair(PartitionedGridLayoutType, gridLayout)
+ }
+
+ @Provides
fun provideGridLayoutMap(
entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>>
): Map<GridLayoutType, GridLayout> {
@@ -103,6 +116,14 @@
}
@Provides
+ @IntoSet
+ fun providePartitionedGridConsistencyInteractor(
+ consistencyInteractor: NoopGridConsistencyInteractor
+ ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
+ return Pair(PartitionedGridLayoutType, consistencyInteractor)
+ }
+
+ @Provides
fun provideGridConsistencyInteractorMap(
entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridTypeConsistencyInteractor>>
): Map<GridLayoutType, GridTypeConsistencyInteractor> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
index 31795d5..44d8688 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
@@ -18,7 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.shared.model.GridLayoutType
-import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -31,7 +31,8 @@
@SysUISingleton
class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository {
- private val _layout: MutableStateFlow<GridLayoutType> = MutableStateFlow(InfiniteGridLayoutType)
+ private val _layout: MutableStateFlow<GridLayoutType> =
+ MutableStateFlow(PartitionedGridLayoutType)
override val layout = _layout.asStateFlow()
override fun setLayout(type: GridLayoutType) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
index 501730a..9550ddb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
@@ -31,3 +31,6 @@
* spaces.
*/
data object StretchedGridLayoutType : GridLayoutType
+
+/** Grid type grouping large tiles on top and icon tiles at the bottom. */
+data object PartitionedGridLayoutType : GridLayoutType
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
new file mode 100644
index 0000000..8d0b386
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
@@ -0,0 +1,252 @@
+/*
+ * 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
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.modifiers.background
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
+import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+@SysUISingleton
+class PartitionedGridLayout
+@Inject
+constructor(
+ private val iconTilesInteractor: IconTilesInteractor,
+ private val gridSizeInteractor: InfiniteGridSizeInteractor,
+) : GridLayout {
+ @Composable
+ override fun TileGrid(tiles: List<TileViewModel>, modifier: Modifier) {
+ DisposableEffect(tiles) {
+ val token = Any()
+ tiles.forEach { it.startListening(token) }
+ onDispose { tiles.forEach { it.stopListening(token) } }
+ }
+ val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+ val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
+ val tileHeight = dimensionResource(id = R.dimen.qs_tile_height)
+ val (smallTiles, largeTiles) = tiles.partition { iconTilesSpecs.contains(it.spec) }
+
+ TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
+ // Large tiles
+ items(largeTiles.size, span = { GridItemSpan(2) }) { index ->
+ Tile(
+ tile = largeTiles[index],
+ iconOnly = false,
+ modifier = Modifier.height(tileHeight)
+ )
+ }
+ fillUpRow(nTiles = largeTiles.size, columns = columns / 2)
+
+ // Small tiles
+ items(smallTiles.size) { index ->
+ Tile(
+ tile = smallTiles[index],
+ iconOnly = true,
+ modifier = Modifier.height(tileHeight)
+ )
+ }
+ }
+ }
+
+ @Composable
+ override fun EditTileGrid(
+ tiles: List<EditTileViewModel>,
+ modifier: Modifier,
+ onAddTile: (TileSpec, Int) -> Unit,
+ onRemoveTile: (TileSpec) -> Unit
+ ) {
+ val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+ val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
+
+ val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
+ val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
+ onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
+ }
+ val isIconOnly: (TileSpec) -> Boolean =
+ remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
+ val tileHeight = dimensionResource(id = R.dimen.qs_tile_height)
+ val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
+
+ Column(
+ verticalArrangement = Arrangement.spacedBy(tilePadding),
+ modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState())
+ ) {
+ CurrentTiles(
+ tiles = currentTiles,
+ tileHeight = tileHeight,
+ tilePadding = tilePadding,
+ onRemoveTile = onRemoveTile,
+ isIconOnly = isIconOnly,
+ columns = columns,
+ )
+ AvailableTiles(
+ tiles = otherTiles,
+ tileHeight = tileHeight,
+ tilePadding = tilePadding,
+ addTileToEnd = addTileToEnd,
+ isIconOnly = isIconOnly,
+ columns = columns,
+ )
+ }
+ }
+
+ @Composable
+ private fun CurrentTiles(
+ tiles: List<EditTileViewModel>,
+ tileHeight: Dp,
+ tilePadding: Dp,
+ onRemoveTile: (TileSpec) -> Unit,
+ isIconOnly: (TileSpec) -> Boolean,
+ columns: Int,
+ ) {
+ val (smallTiles, largeTiles) = tiles.partition { isIconOnly(it.tileSpec) }
+
+ val largeGridHeight = gridHeight(largeTiles.size, tileHeight, columns / 2, tilePadding)
+ val smallGridHeight = gridHeight(smallTiles.size, tileHeight, columns, tilePadding)
+
+ CurrentTilesContainer {
+ TileLazyGrid(
+ columns = GridCells.Fixed(columns),
+ modifier = Modifier.height(largeGridHeight),
+ ) {
+ editTiles(largeTiles, ClickAction.REMOVE, onRemoveTile, { false }, true)
+ }
+ }
+ CurrentTilesContainer {
+ TileLazyGrid(
+ columns = GridCells.Fixed(columns),
+ modifier = Modifier.height(smallGridHeight),
+ ) {
+ editTiles(smallTiles, ClickAction.REMOVE, onRemoveTile, { true }, true)
+ }
+ }
+ }
+
+ @Composable
+ private fun AvailableTiles(
+ tiles: List<EditTileViewModel>,
+ tileHeight: Dp,
+ tilePadding: Dp,
+ addTileToEnd: (TileSpec) -> Unit,
+ isIconOnly: (TileSpec) -> Boolean,
+ columns: Int,
+ ) {
+ val (tilesStock, tilesCustom) = tiles.partition { it.appName == null }
+ val (smallTiles, largeTiles) = tilesStock.partition { isIconOnly(it.tileSpec) }
+
+ val largeGridHeight = gridHeight(largeTiles.size, tileHeight, columns / 2, tilePadding)
+ val smallGridHeight = gridHeight(smallTiles.size, tileHeight, columns, tilePadding)
+ val largeGridHeightCustom =
+ gridHeight(tilesCustom.size, tileHeight, columns / 2, tilePadding)
+
+ // Add up the height of all three grids and add padding in between
+ val gridHeight =
+ largeGridHeight + smallGridHeight + largeGridHeightCustom + (tilePadding * 2)
+
+ AvailableTilesContainer {
+ TileLazyGrid(
+ columns = GridCells.Fixed(columns),
+ modifier = Modifier.height(gridHeight),
+ ) {
+ // Large tiles
+ editTiles(largeTiles, ClickAction.ADD, addTileToEnd, isIconOnly)
+ fillUpRow(nTiles = largeTiles.size, columns = columns / 2)
+
+ // Small tiles
+ editTiles(smallTiles, ClickAction.ADD, addTileToEnd, isIconOnly)
+ fillUpRow(nTiles = smallTiles.size, columns = columns)
+
+ // Custom tiles, all large
+ editTiles(tilesCustom, ClickAction.ADD, addTileToEnd, isIconOnly)
+ }
+ }
+ }
+
+ @Composable
+ private fun CurrentTilesContainer(content: @Composable () -> Unit) {
+ Box(
+ Modifier.fillMaxWidth()
+ .border(
+ width = 1.dp,
+ color = MaterialTheme.colorScheme.onBackground,
+ shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))
+ )
+ .padding(dimensionResource(R.dimen.qs_tile_margin_vertical))
+ ) {
+ content()
+ }
+ }
+
+ @Composable
+ private fun AvailableTilesContainer(content: @Composable () -> Unit) {
+ Box(
+ Modifier.fillMaxWidth()
+ .background(
+ color = MaterialTheme.colorScheme.surfaceVariant,
+ alpha = { 1f },
+ shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))
+ )
+ .padding(dimensionResource(R.dimen.qs_tile_margin_vertical))
+ ) {
+ content()
+ }
+ }
+
+ private fun gridHeight(nTiles: Int, tileHeight: Dp, columns: Int, padding: Dp): Dp {
+ val rows = (nTiles + columns - 1) / columns
+ return ((tileHeight + padding) * rows) - padding
+ }
+
+ /** Fill up the rest of the row if it's not complete. */
+ private fun LazyGridScope.fillUpRow(nTiles: Int, columns: Int) {
+ if (nTiles % columns != 0) {
+ item(span = { GridItemSpan(maxCurrentLineSpan) }) { Spacer(Modifier) }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index eb45110..e8c65a5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -208,7 +208,7 @@
}
}
-private fun LazyGridScope.editTiles(
+fun LazyGridScope.editTiles(
tiles: List<EditTileViewModel>,
clickAction: ClickAction,
onClick: (TileSpec) -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
index 69f50a7..5b4186c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
@@ -22,12 +22,12 @@
import com.android.systemui.qs.panels.domain.interactor.GridLayoutTypeInteractor
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
-import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.domain.interactor.MinimumTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
+import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -47,7 +47,7 @@
private val editTilesListInteractor: EditTilesListInteractor,
private val currentTilesInteractor: CurrentTilesInteractor,
private val minTilesInteractor: MinimumTilesInteractor,
- private val defaultGridLayout: InfiniteGridLayout,
+ @Named("Default") private val defaultGridLayout: GridLayout,
@Application private val applicationScope: CoroutineScope,
gridLayoutTypeInteractor: GridLayoutTypeInteractor,
gridLayoutMap: Map<GridLayoutType, @JvmSuppressWildcards GridLayout>,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt
index 5eee691..127ecb2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt
@@ -21,9 +21,9 @@
import com.android.systemui.qs.panels.domain.interactor.GridLayoutTypeInteractor
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
-import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import javax.inject.Inject
+import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -41,7 +41,7 @@
gridLayoutTypeInteractor: GridLayoutTypeInteractor,
gridLayoutMap: Map<GridLayoutType, @JvmSuppressWildcards GridLayout>,
tilesInteractor: CurrentTilesInteractor,
- defaultGridLayout: InfiniteGridLayout,
+ @Named("Default") defaultGridLayout: GridLayout,
@Application private val applicationScope: CoroutineScope
) {
val gridLayout: StateFlow<GridLayout> =
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 0d0f6e0..b1700e3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -315,9 +315,17 @@
return false
}
- check(to != Scenes.Gone || deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked) {
- "Cannot change to the Gone scene while the device is locked. Logging reason for scene" +
- " change was: $loggingReason"
+ val inMidTransitionFromGone =
+ (transitionState.value as? ObservableTransitionState.Transition)?.fromScene ==
+ Scenes.Gone
+ val isChangeAllowed =
+ to != Scenes.Gone ||
+ inMidTransitionFromGone ||
+ deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked
+ check(isChangeAllowed) {
+ "Cannot change to the Gone scene while the device is locked and not currently" +
+ " transitioning from Gone. Current transition state is ${transitionState.value}." +
+ " Logging reason for scene change was: $loggingReason"
}
return from != to
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 3ce12dd..0304e73 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -45,6 +45,7 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.FalsingManager.FalsingBeliefListener
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.scene.data.model.asIterable
import com.android.systemui.scene.domain.interactor.SceneBackInteractor
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
@@ -365,6 +366,29 @@
private fun handlePowerState() {
applicationScope.launch {
+ powerInteractor.detailedWakefulness.collect { wakefulness ->
+ // Detect a double-tap-power-button gesture that was started while the device was
+ // still awake.
+ if (wakefulness.isAsleep()) return@collect
+ if (!wakefulness.powerButtonLaunchGestureTriggered) return@collect
+ if (wakefulness.lastSleepReason != WakeSleepReason.POWER_BUTTON) return@collect
+
+ // If we're mid-transition from Gone to Lockscreen due to the first power button
+ // press, then return to Gone.
+ val transition: ObservableTransitionState.Transition =
+ sceneInteractor.transitionState.value as? ObservableTransitionState.Transition
+ ?: return@collect
+ if (
+ transition.fromScene == Scenes.Gone && transition.toScene == Scenes.Lockscreen
+ ) {
+ switchToScene(
+ targetSceneKey = Scenes.Gone,
+ loggingReason = "double-tap power gesture",
+ )
+ }
+ }
+ }
+ applicationScope.launch {
powerInteractor.isAsleep.collect { isAsleep ->
if (isAsleep) {
switchToScene(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
new file mode 100644
index 0000000..e85df0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.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.statusbar.chips.call.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.chips.domain.interactor.OngoingActivityChipInteractor
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Interactor for the ongoing phone call chip shown in the status bar. */
+@SysUISingleton
+open class CallChipInteractor @Inject constructor() : OngoingActivityChipInteractor {
+ // TODO(b/332662551): Implement this flow.
+ override val chip: StateFlow<OngoingActivityChipModel> =
+ MutableStateFlow(OngoingActivityChipModel.Hidden)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/domain/interactor/OngoingActivityChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/domain/interactor/OngoingActivityChipInteractor.kt
new file mode 100644
index 0000000..70362c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/domain/interactor/OngoingActivityChipInteractor.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.chips.domain.interactor
+
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import kotlinx.coroutines.flow.StateFlow
+
+/** Interface for an interactor that knows the state of a single type of ongoing activity chip. */
+interface OngoingActivityChipInteractor {
+ /** A flow modeling the chip that should be shown. */
+ val chip: StateFlow<OngoingActivityChipModel>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
new file mode 100644
index 0000000..6f16969
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.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.statusbar.chips.screenrecord.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.domain.interactor.OngoingActivityChipInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Interactor for the screen recording chip shown in the status bar. */
+@SysUISingleton
+open class ScreenRecordChipInteractor @Inject constructor() : OngoingActivityChipInteractor {
+ // TODO(b/332662551): Implement this flow.
+ override val chip: StateFlow<OngoingActivityChipModel> =
+ MutableStateFlow(OngoingActivityChipModel.Hidden)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
new file mode 100644
index 0000000..e63713b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.chips.ui.model
+
+import android.view.View
+import com.android.systemui.common.shared.model.Icon
+
+/** Model representing the display of an ongoing activity as a chip in the status bar. */
+sealed class OngoingActivityChipModel {
+ /** This chip shouldn't be shown. */
+ data object Hidden : OngoingActivityChipModel()
+
+ /** This chip should be shown with the given information. */
+ data class Shown(
+ /** The icon to show on the chip. */
+ val icon: Icon,
+ /**
+ * The time this event started, used to show the timer.
+ *
+ * This time should be relative to
+ * [com.android.systemui.util.time.SystemClock.elapsedRealtime], *not*
+ * [com.android.systemui.util.time.SystemClock.currentTimeMillis] because the
+ * [ChipChronometer] is based off of elapsed realtime. See
+ * [android.widget.Chronometer.setBase].
+ */
+ val startTimeMs: Long,
+ /** Listener method to invoke when this chip is clicked. */
+ val onClickListener: View.OnClickListener,
+ ) : OngoingActivityChipModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
new file mode 100644
index 0000000..47b2b03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.chips.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
+import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * View model deciding which ongoing activity chip to show in the status bar.
+ *
+ * There may be multiple ongoing activities at the same time, but we can only ever show one chip at
+ * any one time (for now). This class decides which ongoing activity to show if there are multiple.
+ */
+@SysUISingleton
+class OngoingActivityChipsViewModel
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ screenRecordChipInteractor: ScreenRecordChipInteractor,
+ callChipInteractor: CallChipInteractor,
+) {
+
+ /**
+ * A flow modeling the chip that should be shown in the status bar after accounting for possibly
+ * multiple ongoing activities.
+ *
+ * [com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment] is responsible for
+ * actually displaying the chip.
+ */
+ val chip: StateFlow<OngoingActivityChipModel> =
+ combine(screenRecordChipInteractor.chip, callChipInteractor.chip) { screenRecord, call ->
+ // This `when` statement shows the priority order of the chips
+ when {
+ screenRecord is OngoingActivityChipModel.Shown -> screenRecord
+ else -> call
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index e5c86c8..97f9e06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -51,6 +51,7 @@
import com.android.systemui.statusbar.OperatorNameView;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
@@ -134,6 +135,8 @@
private final CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger;
private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
private final OngoingCallController mOngoingCallController;
+ // TODO(b/332662551): Use this view model to show the ongoing activity chips.
+ private final OngoingActivityChipsViewModel mOngoingActivityChipsViewModel;
private final SystemStatusAnimationScheduler mAnimationScheduler;
private final StatusBarLocationPublisher mLocationPublisher;
private final NotificationIconAreaController mNotificationIconAreaController;
@@ -217,6 +220,7 @@
public CollapsedStatusBarFragment(
StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
OngoingCallController ongoingCallController,
+ OngoingActivityChipsViewModel ongoingActivityChipsViewModel,
SystemStatusAnimationScheduler animationScheduler,
StatusBarLocationPublisher locationPublisher,
NotificationIconAreaController notificationIconAreaController,
@@ -242,6 +246,7 @@
DemoModeController demoModeController) {
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
mOngoingCallController = ongoingCallController;
+ mOngoingActivityChipsViewModel = ongoingActivityChipsViewModel;
mAnimationScheduler = animationScheduler;
mLocationPublisher = locationPublisher;
mNotificationIconAreaController = notificationIconAreaController;
diff --git a/packages/SystemUI/src/com/android/systemui/theme/CustomDynamicColors.java b/packages/SystemUI/src/com/android/systemui/theme/CustomDynamicColors.java
deleted file mode 100644
index efeb2f9..0000000
--- a/packages/SystemUI/src/com/android/systemui/theme/CustomDynamicColors.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * 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.theme;
-
-import com.google.ux.material.libmonet.dynamiccolor.ContrastCurve;
-import com.google.ux.material.libmonet.dynamiccolor.DynamicColor;
-import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors;
-import com.google.ux.material.libmonet.dynamiccolor.ToneDeltaPair;
-import com.google.ux.material.libmonet.dynamiccolor.TonePolarity;
-
-class CustomDynamicColors {
- private final MaterialDynamicColors mMdc;
-
- CustomDynamicColors(boolean isExtendedFidelity) {
- this.mMdc = new MaterialDynamicColors(isExtendedFidelity);
- }
-
- // CLOCK COLORS
-
- public DynamicColor widgetBackground() {
- return new DynamicColor(
- /* name= */ "widget_background",
- /* palette= */ (s) -> s.primaryPalette,
- /* tone= */ (s) -> s.isDark ? 20.0 : 95.0,
- /* isBackground= */ true,
- /* background= */ null,
- /* secondBackground= */ null,
- /* contrastCurve= */ null,
- /* toneDeltaPair= */ null);
- }
-
- public DynamicColor clockHour() {
- return new DynamicColor(
- /* name= */ "clock_hour",
- /* palette= */ (s) -> s.secondaryPalette,
- /* tone= */ (s) -> s.isDark ? 30.0 : 60.0,
- /* isBackground= */ false,
- /* background= */ (s) -> widgetBackground(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 4.0, 5.0, 15.0),
- /* toneDeltaPair= */
- (s) -> new ToneDeltaPair(clockHour(), clockMinute(), 10.0, TonePolarity.DARKER,
- false));
- }
-
- public DynamicColor clockMinute() {
- return new DynamicColor(
- /* name= */ "clock_minute",
- /* palette= */ (s) -> s.primaryPalette,
- /* tone= */ (s) -> s.isDark ? 40.0 : 90.0,
- /* isBackground= */ false,
- /* background= */ (s) -> widgetBackground(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 6.5, 10.0, 15.0),
- /* toneDeltaPair= */ null);
- }
-
- public DynamicColor clockSecond() {
- return new DynamicColor(
- /* name= */ "clock_second",
- /* palette= */ (s) -> s.tertiaryPalette,
- /* tone= */ (s) -> s.isDark ? 40.0 : 90.0,
- /* isBackground= */ false,
- /* background= */ (s) -> widgetBackground(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 5.0, 70.0, 11.0),
- /* toneDeltaPair= */ null);
- }
-
- public DynamicColor weatherTemp() {
- return new DynamicColor(
- /* name= */ "clock_second",
- /* palette= */ (s) -> s.primaryPalette,
- /* tone= */ (s) -> s.isDark ? 55.0 : 80.0,
- /* isBackground= */ false,
- /* background= */ (s) -> widgetBackground(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 5.0, 70.0, 11.0),
- /* toneDeltaPair= */ null);
- }
-
- // THEME APP ICONS
-
- public DynamicColor themeApp() {
- return new DynamicColor(
- /* name= */ "theme_app",
- /* palette= */ (s) -> s.primaryPalette,
- /* tone= */ (s) -> s.isDark ? 90.0 : 30.0, // Adjusted values
- /* isBackground= */ true,
- /* background= */ null,
- /* secondBackground= */ null,
- /* contrastCurve= */ null,
- /* toneDeltaPair= */ null);
- }
-
- public DynamicColor onThemeApp() {
- return new DynamicColor(
- /* name= */ "on_theme_app",
- /* palette= */ (s) -> s.primaryPalette,
- /* tone= */ (s) -> s.isDark ? 40.0 : 80.0, // Adjusted values
- /* isBackground= */ false,
- /* background= */ (s) -> themeApp(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 7.0, 10.0),
- /* toneDeltaPair= */ null);
- }
-
- public DynamicColor themeAppRing() {
- return new DynamicColor(
- /* name= */ "theme_app_ring",
- /* palette= */ (s) -> s.primaryPalette,
- /* tone= */ (s) -> 70.0,
- /* isBackground= */ true,
- /* background= */ null,
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0),
- /* toneDeltaPair= */ null);
- }
-
- public DynamicColor themeNotif() {
- return new DynamicColor(
- /* name= */ "theme_notif",
- /* palette= */ (s) -> s.tertiaryPalette,
- /* tone= */ (s) -> s.isDark ? 80.0 : 90.0,
- /* isBackground= */ false,
- /* background= */ (s) -> themeAppRing(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0),
- /* toneDeltaPair= */
- (s) -> new ToneDeltaPair(themeNotif(), themeAppRing(), 10.0, TonePolarity.NEARER,
- false));
- }
-
- // SUPER G COLORS
-
- public DynamicColor brandA() {
- return new DynamicColor(
- /* name= */ "brand_a",
- /* palette= */ (s) -> s.primaryPalette,
- /* tone= */ (s) -> s.isDark ? 40.0 : 80.0,
- /* isBackground= */ true,
- /* background= */ (s) -> mMdc.surfaceContainerLow(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 7.0, 17.0),
- /* toneDeltaPair= */
- (s) -> new ToneDeltaPair(brandA(), brandB(), 10.0, TonePolarity.NEARER, false));
- }
-
- public DynamicColor brandB() {
- return new DynamicColor(
- /* name= */ "brand_b",
- /* palette= */ (s) -> s.secondaryPalette,
- /* tone= */ (s) -> s.isDark ? 70.0 : 98.0,
- /* isBackground= */ true,
- /* background= */ (s) -> mMdc.surfaceContainerLow(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 3.0, 6.0),
- /* toneDeltaPair= */
- (s) -> new ToneDeltaPair(brandB(), brandC(), 10.0, TonePolarity.NEARER, false));
- }
-
- public DynamicColor brandC() {
- return new DynamicColor(
- /* name= */ "brand_c",
- /* palette= */ (s) -> s.primaryPalette,
- /* tone= */ (s) -> s.isDark ? 50.0 : 60.0,
- /* isBackground= */ false,
- /* background= */ (s) -> mMdc.surfaceContainerLow(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 4.0, 9.0),
- /* toneDeltaPair= */
- (s) -> new ToneDeltaPair(brandC(), brandD(), 10.0, TonePolarity.NEARER, false));
- }
-
- public DynamicColor brandD() {
- return new DynamicColor(
- /* name= */ "brand_d",
- /* palette= */ (s) -> s.tertiaryPalette,
- /* tone= */ (s) -> s.isDark ? 59.0 : 90.0,
- /* isBackground= */ false,
- /* background= */ (s) -> mMdc.surfaceContainerLow(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 4.0, 13.0),
- /* toneDeltaPair= */
- (s) -> new ToneDeltaPair(brandD(), brandA(), 10.0, TonePolarity.NEARER, false));
- }
-
- // QUICK SETTING TIILES
-
- public DynamicColor underSurface() {
- return new DynamicColor(
- /* name= */ "under_surface",
- /* palette= */ (s) -> s.primaryPalette,
- /* tone= */ (s) -> 0.0,
- /* isBackground= */ true,
- /* background= */ null,
- /* secondBackground= */ null,
- /* contrastCurve= */ null,
- /* toneDeltaPair= */ null);
- }
-
- public DynamicColor shadeActive() {
- return new DynamicColor(
- /* name= */ "shade_active",
- /* palette= */ (s) -> s.primaryPalette,
- /* tone= */ (s) -> 90.0,
- /* isBackground= */ false,
- /* background= */ (s) -> underSurface(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 4.5, 7.0),
- /* toneDeltaPair= */
- (s) -> new ToneDeltaPair(shadeActive(), shadeInactive(), 30.0, TonePolarity.LIGHTER,
- false));
- }
-
- public DynamicColor onShadeActive() {
- return new DynamicColor(
- /* name= */ "on_shade_active",
- /* palette= */ (s) -> s.primaryPalette,
- /* tone= */ (s) -> 10.0,
- /* isBackground= */ false,
- /* background= */ (s) -> shadeActive(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0),
- /* toneDeltaPair= */
- (s) -> new ToneDeltaPair(onShadeActive(), onShadeActiveVariant(), 20.0,
- TonePolarity.NEARER, false));
- }
-
- public DynamicColor onShadeActiveVariant() {
- return new DynamicColor(
- /* name= */ "on_shade_active_variant",
- /* palette= */ (s) -> s.primaryPalette,
- /* tone= */ (s) -> 30.0,
- /* isBackground= */ false,
- /* background= */ (s) -> shadeActive(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0),
- /* toneDeltaPair= */
- (s) -> new ToneDeltaPair(onShadeActiveVariant(), onShadeActive(), 20.0,
- TonePolarity.NEARER, false));
- }
-
- public DynamicColor shadeInactive() {
- return new DynamicColor(
- /* name= */ "shade_inactive",
- /* palette= */ (s) -> s.neutralPalette,
- /* tone= */ (s) -> 20.0,
- /* isBackground= */ true,
- /* background= */ (s) -> underSurface(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0),
- /* toneDeltaPair= */(s) -> new ToneDeltaPair(shadeInactive(), shadeDisabled(), 15.0,
- TonePolarity.LIGHTER, false));
- }
-
- public DynamicColor onShadeInactive() {
- return new DynamicColor(
- /* name= */ "on_shade_inactive",
- /* palette= */ (s) -> s.neutralVariantPalette,
- /* tone= */ (s) -> 90.0,
- /* isBackground= */ true,
- /* background= */ (s) -> shadeInactive(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0),
- /* toneDeltaPair= */
- (s) -> new ToneDeltaPair(onShadeInactive(), onShadeInactiveVariant(), 10.0,
- TonePolarity.NEARER, false));
- }
-
- public DynamicColor onShadeInactiveVariant() {
- return new DynamicColor(
- /* name= */ "on_shade_inactive_variant",
- /* palette= */ (s) -> s.neutralVariantPalette,
- /* tone= */ (s) -> 80.0,
- /* isBackground= */ false,
- /* background= */ (s) -> shadeInactive(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0),
- /* toneDeltaPair= */
- (s) -> new ToneDeltaPair(onShadeInactiveVariant(), onShadeInactive(), 10.0,
- TonePolarity.NEARER, false));
- }
-
- public DynamicColor shadeDisabled() {
- return new DynamicColor(
- /* name= */ "shade_disabled",
- /* palette= */ (s) -> s.neutralPalette,
- /* tone= */ (s) -> 4.0,
- /* isBackground= */ false,
- /* background= */ (s) -> underSurface(),
- /* secondBackground= */ null,
- /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0),
- /* toneDeltaPair= */ null);
- }
-
- public DynamicColor overviewBackground() {
- return new DynamicColor(
- /* name= */ "overview_background",
- /* palette= */ (s) -> s.neutralVariantPalette,
- /* tone= */ (s) -> s.isDark ? 80.0 : 35.0,
- /* isBackground= */ true,
- /* background= */ null,
- /* secondBackground= */ null,
- /* contrastCurve= */null,
- /* toneDeltaPair= */ null);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
deleted file mode 100644
index 3518759..0000000
--- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
+++ /dev/null
@@ -1,135 +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.systemui.theme
-
-import android.util.Pair
-import com.google.ux.material.libmonet.dynamiccolor.DynamicColor
-import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors
-
-class DynamicColors {
- companion object {
- @JvmStatic
- fun allDynamicColorsMapped(isExtendedFidelity: Boolean): List<Pair<String, DynamicColor>> {
- val mdc = MaterialDynamicColors(isExtendedFidelity)
- return arrayListOf(
- Pair.create("primary_container", mdc.primaryContainer()),
- Pair.create("on_primary_container", mdc.onPrimaryContainer()),
- Pair.create("primary", mdc.primary()),
- Pair.create("on_primary", mdc.onPrimary()),
- Pair.create("secondary_container", mdc.secondaryContainer()),
- Pair.create("on_secondary_container", mdc.onSecondaryContainer()),
- Pair.create("secondary", mdc.secondary()),
- Pair.create("on_secondary", mdc.onSecondary()),
- Pair.create("tertiary_container", mdc.tertiaryContainer()),
- Pair.create("on_tertiary_container", mdc.onTertiaryContainer()),
- Pair.create("tertiary", mdc.tertiary()),
- Pair.create("on_tertiary", mdc.onTertiary()),
- Pair.create("background", mdc.background()),
- Pair.create("on_background", mdc.onBackground()),
- Pair.create("surface", mdc.surface()),
- Pair.create("on_surface", mdc.onSurface()),
- Pair.create("surface_container_low", mdc.surfaceContainerLow()),
- Pair.create("surface_container_lowest", mdc.surfaceContainerLowest()),
- Pair.create("surface_container", mdc.surfaceContainer()),
- Pair.create("surface_container_high", mdc.surfaceContainerHigh()),
- Pair.create("surface_container_highest", mdc.surfaceContainerHighest()),
- Pair.create("surface_bright", mdc.surfaceBright()),
- Pair.create("surface_dim", mdc.surfaceDim()),
- Pair.create("surface_variant", mdc.surfaceVariant()),
- Pair.create("on_surface_variant", mdc.onSurfaceVariant()),
- Pair.create("outline", mdc.outline()),
- Pair.create("outline_variant", mdc.outlineVariant()),
- Pair.create("error", mdc.error()),
- Pair.create("on_error", mdc.onError()),
- Pair.create("error_container", mdc.errorContainer()),
- Pair.create("on_error_container", mdc.onErrorContainer()),
- Pair.create("control_activated", mdc.controlActivated()),
- Pair.create("control_normal", mdc.controlNormal()),
- Pair.create("control_highlight", mdc.controlHighlight()),
- Pair.create("text_primary_inverse", mdc.textPrimaryInverse()),
- Pair.create(
- "text_secondary_and_tertiary_inverse",
- mdc.textSecondaryAndTertiaryInverse()
- ),
- Pair.create(
- "text_primary_inverse_disable_only",
- mdc.textPrimaryInverseDisableOnly()
- ),
- Pair.create(
- "text_secondary_and_tertiary_inverse_disabled",
- mdc.textSecondaryAndTertiaryInverseDisabled()
- ),
- Pair.create("text_hint_inverse", mdc.textHintInverse()),
- Pair.create("palette_key_color_primary", mdc.primaryPaletteKeyColor()),
- Pair.create("palette_key_color_secondary", mdc.secondaryPaletteKeyColor()),
- Pair.create("palette_key_color_tertiary", mdc.tertiaryPaletteKeyColor()),
- Pair.create("palette_key_color_neutral", mdc.neutralPaletteKeyColor()),
- Pair.create(
- "palette_key_color_neutral_variant",
- mdc.neutralVariantPaletteKeyColor()
- ),
- )
- }
-
- @JvmStatic
- fun getFixedColorsMapped(isExtendedFidelity: Boolean): List<Pair<String, DynamicColor>> {
- val mdc = MaterialDynamicColors(isExtendedFidelity)
- return arrayListOf(
- Pair.create("primary_fixed", mdc.primaryFixed()),
- Pair.create("primary_fixed_dim", mdc.primaryFixedDim()),
- Pair.create("on_primary_fixed", mdc.onPrimaryFixed()),
- Pair.create("on_primary_fixed_variant", mdc.onPrimaryFixedVariant()),
- Pair.create("secondary_fixed", mdc.secondaryFixed()),
- Pair.create("secondary_fixed_dim", mdc.secondaryFixedDim()),
- Pair.create("on_secondary_fixed", mdc.onSecondaryFixed()),
- Pair.create("on_secondary_fixed_variant", mdc.onSecondaryFixedVariant()),
- Pair.create("tertiary_fixed", mdc.tertiaryFixed()),
- Pair.create("tertiary_fixed_dim", mdc.tertiaryFixedDim()),
- Pair.create("on_tertiary_fixed", mdc.onTertiaryFixed()),
- Pair.create("on_tertiary_fixed_variant", mdc.onTertiaryFixedVariant()),
- )
- }
-
- @JvmStatic
- fun getCustomColorsMapped(isExtendedFidelity: Boolean): List<Pair<String, DynamicColor>> {
- val customMdc = CustomDynamicColors(isExtendedFidelity)
- return arrayListOf(
- Pair.create("widget_background", customMdc.widgetBackground()),
- Pair.create("clock_hour", customMdc.clockHour()),
- Pair.create("clock_minute", customMdc.clockMinute()),
- Pair.create("clock_second", customMdc.weatherTemp()),
- Pair.create("theme_app", customMdc.themeApp()),
- Pair.create("on_theme_app", customMdc.onThemeApp()),
- Pair.create("theme_app_ring", customMdc.themeAppRing()),
- Pair.create("on_theme_app_ring", customMdc.themeNotif()),
- Pair.create("brand_a", customMdc.brandA()),
- Pair.create("brand_b", customMdc.brandB()),
- Pair.create("brand_c", customMdc.brandC()),
- Pair.create("brand_d", customMdc.brandD()),
- Pair.create("under_surface", customMdc.underSurface()),
- Pair.create("shade_active", customMdc.shadeActive()),
- Pair.create("on_shade_active", customMdc.onShadeActive()),
- Pair.create("on_shade_active_variant", customMdc.onShadeActiveVariant()),
- Pair.create("shade_inactive", customMdc.shadeInactive()),
- Pair.create("on_shade_inactive", customMdc.onShadeInactive()),
- Pair.create("on_shade_inactive_variant", customMdc.onShadeInactiveVariant()),
- Pair.create("shade_disabled", customMdc.shadeDisabled()),
- Pair.create("overview_background", customMdc.overviewBackground())
- )
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index d256c4a..7494649 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -77,6 +77,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.monet.ColorScheme;
+import com.android.systemui.monet.DynamicColors;
import com.android.systemui.monet.Style;
import com.android.systemui.monet.TonalPalette;
import com.android.systemui.settings.UserTracker;
@@ -623,7 +624,7 @@
TonalPalette tonalPalette) {
String resourcePrefix = "android:color/system_" + name;
- tonalPalette.getAllShadesMapped().forEach((key, value) -> {
+ tonalPalette.allShadesMapped.forEach((key, value) -> {
String resourceName = resourcePrefix + "_" + key;
int colorValue = ColorUtils.setAlphaComponent(value, 0xFF);
overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
@@ -634,7 +635,7 @@
protected FabricatedOverlay createDynamicOverlay() {
FabricatedOverlay overlay = newFabricatedOverlay("dynamic");
//Themed Colors
- assignColorsToOverlay(overlay, DynamicColors.allDynamicColorsMapped(mIsFidelityEnabled),
+ assignColorsToOverlay(overlay, DynamicColors.getAllDynamicColorsMapped(mIsFidelityEnabled),
false);
// Fixed Colors
assignColorsToOverlay(overlay, DynamicColors.getFixedColorsMapped(mIsFidelityEnabled),
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index c08cd64..fd01b48 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -77,8 +77,6 @@
mapOf(
AudioStream(AudioManager.STREAM_NOTIFICATION) to
R.string.stream_notification_unavailable,
- AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm_unavailable,
- AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable,
)
private val uiEventByStream =
mapOf(
@@ -126,7 +124,7 @@
}
}
- private suspend fun AudioStreamModel.toState(
+ private fun AudioStreamModel.toState(
isEnabled: Boolean,
ringerMode: RingerMode,
): State {
@@ -138,7 +136,13 @@
valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
icon = getIcon(ringerMode),
label = label,
- disabledMessage = disabledTextByStream[audioStream]?.let(context::getString),
+ disabledMessage =
+ context.getString(
+ disabledTextByStream.getOrDefault(
+ audioStream,
+ R.string.stream_alarm_unavailable,
+ )
+ ),
isEnabled = isEnabled,
a11yStep = volumeRange.step,
a11yClickDescription =
@@ -167,14 +171,13 @@
null
},
audioStreamModel = this,
- isMutable = audioVolumeInteractor.isAffectedByMute(audioStream),
+ isMutable = isAffectedByMute,
)
}
private fun AudioStreamModel.getIcon(ringerMode: RingerMode): Icon {
- val isMutedOrNoVolume = isMuted || volume == minVolume
val iconRes =
- if (isMutedOrNoVolume) {
+ if (isAffectedByMute && isMuted) {
if (audioStream.value in streamsAffectedByRing) {
if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
R.drawable.ic_volume_ringer_vibrate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt
deleted file mode 100644
index 85cc88d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt
+++ /dev/null
@@ -1,266 +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.systemui.monet
-
-import android.testing.AndroidTestingRunner
-import android.util.Log
-import android.util.Pair
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.theme.DynamicColors
-import com.google.ux.material.libmonet.dynamiccolor.DynamicColor
-import com.google.ux.material.libmonet.hct.Hct
-import com.google.ux.material.libmonet.scheme.SchemeTonalSpot
-import java.io.File
-import java.io.FileWriter
-import java.io.StringWriter
-import javax.xml.parsers.DocumentBuilderFactory
-import javax.xml.transform.OutputKeys
-import javax.xml.transform.TransformerException
-import javax.xml.transform.TransformerFactory
-import javax.xml.transform.dom.DOMSource
-import javax.xml.transform.stream.StreamResult
-import kotlin.math.abs
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.w3c.dom.Document
-import org.w3c.dom.Element
-import org.w3c.dom.Node
-
-private const val fileHeader =
- """
- ~ 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.
-"""
-
-private fun testName(name: String): String {
- return "Auto generated by: atest ColorSchemeTest#$name"
-}
-
-private const val commentRoles =
- "Colors used in Android system, from design system. These " +
- "values can be overlaid at runtime by OverlayManager RROs."
-
-private const val commentOverlay = "This value can be overlaid at runtime by OverlayManager RROs."
-
-private fun commentWhite(paletteName: String): String {
- return "Lightest shade of the $paletteName color used by the system. White. $commentOverlay"
-}
-
-private fun commentBlack(paletteName: String): String {
- return "Darkest shade of the $paletteName color used by the system. Black. $commentOverlay"
-}
-
-private fun commentShade(paletteName: String, tone: Int): String {
- return "Shade of the $paletteName system color at $tone% perceptual luminance (L* in L*a*b* " +
- "color space). $commentOverlay"
-}
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class ColorSchemeTest : SysuiTestCase() {
- private val defaultContrast = 0.0
- private val defaultIsDark = false
- private val defaultIsFidelity = false
-
- @Test
- fun generateThemeStyles() {
- val document = buildDoc<Any>()
-
- val themes = document.createElement("themes")
- document.appendWithBreak(themes)
-
- var hue = 0.0
- while (hue < 360) {
- val sourceColor = Hct.from(hue, 50.0, 50.0)
- val sourceColorHex = sourceColor.toInt().toRGBHex()
-
- val theme = document.createElement("theme")
- theme.setAttribute("color", sourceColorHex)
- themes.appendChild(theme)
-
- for (styleValue in Style.entries) {
- if (
- styleValue == Style.CLOCK ||
- styleValue == Style.CLOCK_VIBRANT ||
- styleValue == Style.CONTENT
- ) {
- continue
- }
-
- val style = document.createElement(styleValue.name.lowercase())
- val colorScheme = ColorScheme(sourceColor.toInt(), defaultIsDark, styleValue)
-
- style.appendChild(
- document.createTextNode(
- listOf(
- colorScheme.accent1,
- colorScheme.accent2,
- colorScheme.accent3,
- colorScheme.neutral1,
- colorScheme.neutral2
- )
- .flatMap { a -> listOf(*a.allShades.toTypedArray()) }
- .joinToString(",", transform = Int::toRGBHex)
- )
- )
- theme.appendChild(style)
- }
-
- hue += 60
- }
-
- saveFile(document, "current_themes.xml")
- }
-
- @Test
- fun generateDefaultValues() {
- val document = buildDoc<Any>()
-
- val resources = document.createElement("resources")
- document.appendWithBreak(resources)
-
- // shade colors
- val colorScheme = ColorScheme(GOOGLE_BLUE, defaultIsDark)
- arrayOf(
- Triple("accent1", "Primary", colorScheme.accent1),
- Triple("accent2", "Secondary", colorScheme.accent2),
- Triple("accent3", "Tertiary", colorScheme.accent3),
- Triple("neutral1", "Neutral", colorScheme.neutral1),
- Triple("neutral2", "Secondary Neutral", colorScheme.neutral2)
- )
- .forEach {
- val (paletteName, readable, palette) = it
- palette.allShadesMapped.entries.forEachIndexed { index, (shade, colorValue) ->
- val comment =
- when (index) {
- 0 -> commentWhite(readable)
- palette.allShadesMapped.entries.size - 1 -> commentBlack(readable)
- else -> commentShade(readable, abs(shade / 10 - 100))
- }
- resources.createColorEntry("system_${paletteName}_$shade", colorValue, comment)
- }
- }
-
- resources.appendWithBreak(document.createComment(commentRoles), 2)
-
- fun generateDynamic(pairs: List<Pair<String, DynamicColor>>) {
- arrayOf(false, true).forEach { isDark ->
- val suffix = if (isDark) "_dark" else "_light"
- val dynamicScheme =
- SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), isDark, defaultContrast)
- pairs.forEach {
- resources.createColorEntry(
- "system_${it.first}$suffix",
- it.second.getArgb(dynamicScheme)
- )
- }
- }
- }
-
- // dynamic colors
- generateDynamic(DynamicColors.allDynamicColorsMapped(defaultIsFidelity))
-
- // fixed colors
- val dynamicScheme =
- SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), defaultIsDark, defaultContrast)
- DynamicColors.getFixedColorsMapped(defaultIsFidelity).forEach {
- resources.createColorEntry("system_${it.first}", it.second.getArgb(dynamicScheme))
- }
-
- resources.appendWithBreak(document.createComment(commentRoles), 2)
-
- // custom colors
- generateDynamic(DynamicColors.getCustomColorsMapped(defaultIsFidelity))
-
- saveFile(document, "role_values.xml")
- }
-
- // Helper Functions
-
- private inline fun <reified T> buildDoc(): Document {
- val functionName = T::class.simpleName + ""
- val factory = DocumentBuilderFactory.newInstance()
- val builder = factory.newDocumentBuilder()
- val document = builder.newDocument()
-
- document.appendWithBreak(document.createComment(fileHeader))
- document.appendWithBreak(document.createComment(testName(functionName)))
-
- return document
- }
-
- private fun documentToString(document: Document): String {
- try {
- val transformerFactory = TransformerFactory.newInstance()
- val transformer = transformerFactory.newTransformer()
- transformer.setOutputProperty(OutputKeys.MEDIA_TYPE, "application/xml")
- transformer.setOutputProperty(OutputKeys.METHOD, "xml")
- transformer.setOutputProperty(OutputKeys.INDENT, "yes")
- transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4")
-
- val stringWriter = StringWriter()
- transformer.transform(DOMSource(document), StreamResult(stringWriter))
- return stringWriter.toString()
- } catch (e: TransformerException) {
- throw RuntimeException("Error transforming XML", e)
- }
- }
-
- private fun saveFile(document: Document, fileName: String) {
- val outPath = context.filesDir.path + "/" + fileName
- Log.d("ColorSchemeXml", "Artifact $fileName created")
- val writer = FileWriter(File(outPath))
- writer.write(documentToString(document))
- writer.close()
- }
-}
-
-private fun Element.createColorEntry(name: String, value: Int, comment: String? = null) {
- val doc = this.ownerDocument
-
- if (comment != null) {
- this.appendChild(doc.createComment(comment))
- }
-
- val color = doc.createElement("color")
- this.appendChild(color)
-
- color.setAttribute("name", name)
- color.appendChild(doc.createTextNode("#" + value.toRGBHex()))
-}
-
-private fun Node.appendWithBreak(child: Node, lineBreaks: Int = 1): Node {
- val doc = if (this is Document) this else this.ownerDocument
- val node = doc.createTextNode("\n".repeat(lineBreaks))
- this.appendChild(node)
- return this.appendChild(child)
-}
-
-private fun Int.toRGBHex(): String {
- return "%06X".format(0xFFFFFF and this)
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
index d15cfbf..2da4b72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
@@ -23,8 +23,8 @@
import com.android.systemui.qs.panels.data.repository.IconTilesRepository
import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
import com.android.systemui.qs.panels.data.repository.iconTilesRepository
-import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -45,8 +45,6 @@
@RunWith(AndroidTestingRunner::class)
class GridConsistencyInteractorTest : SysuiTestCase() {
- data object TestGridLayoutType : GridLayoutType
-
private val iconOnlyTiles =
MutableStateFlow(
setOf(
@@ -65,17 +63,13 @@
override val iconTilesSpecs: StateFlow<Set<TileSpec>>
get() = iconOnlyTiles.asStateFlow()
}
- gridConsistencyInteractorsMap =
- mapOf(
- Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor),
- Pair(TestGridLayoutType, noopGridConsistencyInteractor)
- )
}
private val underTest = with(kosmos) { gridConsistencyInteractor }
@Before
fun setUp() {
+ // Mostly testing InfiniteGridConsistencyInteractor because it reorders tiles
with(kosmos) { gridLayoutTypeRepository.setLayout(InfiniteGridLayoutType) }
underTest.start()
}
@@ -86,7 +80,7 @@
with(kosmos) {
testScope.runTest {
// Using the no-op grid consistency interactor
- gridLayoutTypeRepository.setLayout(TestGridLayoutType)
+ gridLayoutTypeRepository.setLayout(PartitionedGridLayoutType)
// Setting an invalid layout with holes
// [ Large A ] [ sa ]
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
new file mode 100644
index 0000000..fa2b343
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.chips.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+class OngoingActivityChipsViewModelTest : SysuiTestCase() {
+
+ private val kosmos = Kosmos()
+ private val underTest = kosmos.ongoingActivityChipsViewModel
+
+ @Test
+ fun chip_allHidden_hidden() =
+ kosmos.testScope.runTest {
+ kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden
+ kosmos.callChipInteractor.chip.value = OngoingActivityChipModel.Hidden
+
+ val latest by collectLastValue(underTest.chip)
+
+ assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden)
+ }
+
+ @Test
+ fun chip_screenRecordShow_restHidden_screenRecordShown() =
+ kosmos.testScope.runTest {
+ val screenRecordChip =
+ OngoingActivityChipModel.Shown(
+ Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("icon")),
+ startTimeMs = 500L,
+ ) {}
+ kosmos.screenRecordChipInteractor.chip.value = screenRecordChip
+ kosmos.callChipInteractor.chip.value = OngoingActivityChipModel.Hidden
+
+ val latest by collectLastValue(underTest.chip)
+
+ assertThat(latest).isEqualTo(screenRecordChip)
+ }
+
+ @Test
+ fun chip_screenRecordShowAndCallShow_screenRecordShown() =
+ kosmos.testScope.runTest {
+ val screenRecordChip =
+ OngoingActivityChipModel.Shown(
+ Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("icon")),
+ startTimeMs = 500L,
+ ) {}
+ kosmos.screenRecordChipInteractor.chip.value = screenRecordChip
+
+ val callChip =
+ OngoingActivityChipModel.Shown(
+ Icon.Resource(R.drawable.ic_call, ContentDescription.Loaded("icon")),
+ startTimeMs = 600L,
+ ) {}
+ kosmos.callChipInteractor.chip.value = callChip
+
+ val latest by collectLastValue(underTest.chip)
+
+ assertThat(latest).isEqualTo(screenRecordChip)
+ }
+
+ @Test
+ fun chip_screenRecordHideAndCallShown_callShown() =
+ kosmos.testScope.runTest {
+ kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden
+
+ val callChip =
+ OngoingActivityChipModel.Shown(
+ Icon.Resource(R.drawable.ic_call, ContentDescription.Loaded("icon")),
+ startTimeMs = 600L,
+ ) {}
+ kosmos.callChipInteractor.chip.value = callChip
+
+ val latest by collectLastValue(underTest.chip)
+
+ assertThat(latest).isEqualTo(callChip)
+ }
+
+ @Test
+ fun chip_higherPriorityChipAdded_lowerPriorityChipReplaced() =
+ kosmos.testScope.runTest {
+ // Start with just the lower priority call chip
+ val callChip =
+ OngoingActivityChipModel.Shown(
+ Icon.Resource(R.drawable.ic_call, ContentDescription.Loaded("icon")),
+ startTimeMs = 600L,
+ ) {}
+ kosmos.callChipInteractor.chip.value = callChip
+ kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden
+
+ val latest by collectLastValue(underTest.chip)
+
+ assertThat(latest).isEqualTo(callChip)
+
+ // WHEN the higher priority screen record chip is added
+ val screenRecordChip =
+ OngoingActivityChipModel.Shown(
+ Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("icon")),
+ startTimeMs = 500L,
+ ) {}
+ kosmos.screenRecordChipInteractor.chip.value = screenRecordChip
+
+ // THEN the higher priority screen record chip is used
+ assertThat(latest).isEqualTo(screenRecordChip)
+ }
+
+ @Test
+ fun chip_highestPriorityChipRemoved_showsNextPriorityChip() =
+ kosmos.testScope.runTest {
+ // Start with both the higher priority screen record chip and lower priority call chip
+ val screenRecordChip =
+ OngoingActivityChipModel.Shown(
+ Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("icon")),
+ startTimeMs = 500L,
+ ) {}
+ kosmos.screenRecordChipInteractor.chip.value = screenRecordChip
+
+ val callChip =
+ OngoingActivityChipModel.Shown(
+ Icon.Resource(R.drawable.ic_call, ContentDescription.Loaded("icon")),
+ startTimeMs = 600L,
+ ) {}
+ kosmos.callChipInteractor.chip.value = callChip
+
+ val latest by collectLastValue(underTest.chip)
+
+ assertThat(latest).isEqualTo(screenRecordChip)
+
+ // WHEN the higher priority screen record is removed
+ kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden
+
+ // THEN the lower priority call is used
+ assertThat(latest).isEqualTo(callChip)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index fdf77ae..ff182ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.plugins.DarkIconDispatcher;
@@ -55,6 +56,7 @@
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.OperatorNameViewController;
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
@@ -90,10 +92,11 @@
@RunWithLooper(setAsMainLooper = true)
@SmallTest
public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
-
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter();
private NotificationIconAreaController mMockNotificationAreaController;
private ShadeExpansionStateManager mShadeExpansionStateManager;
private OngoingCallController mOngoingCallController;
+ private OngoingActivityChipsViewModel mOngoingActivityChipsViewModel;
private SystemStatusAnimationScheduler mAnimationScheduler;
private StatusBarLocationPublisher mLocationPublisher;
// Set in instantiate()
@@ -667,6 +670,7 @@
MockitoAnnotations.initMocks(this);
setUpDaggerComponent();
mOngoingCallController = mock(OngoingCallController.class);
+ mOngoingActivityChipsViewModel = mKosmos.getOngoingActivityChipsViewModel();
mAnimationScheduler = mock(SystemStatusAnimationScheduler.class);
mLocationPublisher = mock(StatusBarLocationPublisher.class);
mStatusBarIconController = mock(StatusBarIconController.class);
@@ -687,6 +691,7 @@
return new CollapsedStatusBarFragment(
mStatusBarFragmentComponentFactory,
mOngoingCallController,
+ mOngoingActivityChipsViewModel,
mAnimationScheduler,
mLocationPublisher,
mMockNotificationAreaController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index a5e7a67..5ad88ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -64,6 +64,7 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.monet.DynamicColors;
import com.android.systemui.monet.Style;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -997,7 +998,7 @@
// All fixed colors were added once
// All custom dynamic tokens added twice
verify(dynamic, times(
- DynamicColors.allDynamicColorsMapped(false).size() * 2
+ DynamicColors.getAllDynamicColorsMapped(false).size() * 2
+ DynamicColors.getFixedColorsMapped(false).size()
+ DynamicColors.getCustomColorsMapped(false).size() * 2)
).setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null));
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index 42b6e18..020f7fa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -66,6 +66,7 @@
FaceWakeUpTriggersConfigModule::class,
]
)
+@Deprecated("Use Kosmos instead. See com.android.systemui.kosmos.Kosmos.")
interface SysUITestModule {
@Binds fun bindTestableContext(sysuiTestableContext: SysuiTestableContext): TestableContext
@@ -127,6 +128,7 @@
}
}
+@Deprecated("Use Kosmos instead. See com.android.systemui.kosmos.Kosmos.")
interface SysUITestComponent<out T> {
val testScope: TestScope
val underTest: T
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 0e95320..f2f4332 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -55,6 +55,7 @@
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shadeController
+import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
@@ -63,11 +64,18 @@
import com.android.systemui.util.time.systemClock
import kotlinx.coroutines.ExperimentalCoroutinesApi
-/** Helper for using [Kosmos] from Java. */
+/**
+ * Helper for using [Kosmos] from Java.
+ *
+ * If your test class extends [SysuiTestCase], you may use the secondary constructor so that
+ * [Kosmos.applicationContext] and [Kosmos.testCase] are automatically set.
+ */
@Deprecated("Please convert your test to Kotlin and use [Kosmos] directly.")
-class KosmosJavaAdapter(
- testCase: SysuiTestCase,
-) {
+class KosmosJavaAdapter() {
+ constructor(testCase: SysuiTestCase) : this() {
+ kosmos.applicationContext = testCase.context
+ kosmos.testCase = testCase
+ }
private val kosmos = Kosmos()
@@ -119,8 +127,5 @@
val shadeRepository by lazy { kosmos.shadeRepository }
val shadeInteractor by lazy { kosmos.shadeInteractor }
- init {
- kosmos.applicationContext = testCase.context
- kosmos.testCase = testCase
- }
+ val ongoingActivityChipsViewModel by lazy { kosmos.ongoingActivityChipsViewModel }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
index 34e99d3..5568c6c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
@@ -20,13 +20,24 @@
import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
val Kosmos.gridLayoutTypeInteractor by
Kosmos.Fixture { GridLayoutTypeInteractor(gridLayoutTypeRepository) }
val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by
- Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) }
+ Kosmos.Fixture {
+ mapOf(
+ Pair(PartitionedGridLayoutType, partitionedGridLayout),
+ Pair(InfiniteGridLayoutType, infiniteGridLayout)
+ )
+ }
var Kosmos.gridConsistencyInteractorsMap: Map<GridLayoutType, GridTypeConsistencyInteractor> by
- Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)) }
+ Kosmos.Fixture {
+ mapOf(
+ Pair(PartitionedGridLayoutType, noopGridConsistencyInteractor),
+ Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
new file mode 100644
index 0000000..4febfe91
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.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.ui.compose.PartitionedGridLayout
+
+val Kosmos.partitionedGridLayout by
+ Kosmos.Fixture { PartitionedGridLayout(iconTilesInteractor, infiniteGridSizeInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
index 9481fca..6625bb5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
@@ -20,7 +20,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
-import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
+import com.android.systemui.qs.panels.domain.interactor.partitionedGridLayout
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
val Kosmos.tileGridViewModel by
@@ -29,7 +29,7 @@
gridLayoutTypeInteractor,
gridLayoutMap,
currentTilesInteractor,
- infiniteGridLayout,
+ partitionedGridLayout,
applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/FakeOngoingActivityChipInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/FakeOngoingActivityChipInteractor.kt
new file mode 100644
index 0000000..cd08274
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/FakeOngoingActivityChipInteractor.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.statusbar.chips.ui.viewmodel
+
+import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
+import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeScreenRecordChipInteractor : ScreenRecordChipInteractor() {
+ override val chip: MutableStateFlow<OngoingActivityChipModel> =
+ MutableStateFlow(OngoingActivityChipModel.Hidden)
+}
+
+class FakeCallChipInteractor : CallChipInteractor() {
+ override val chip: MutableStateFlow<OngoingActivityChipModel> =
+ MutableStateFlow(OngoingActivityChipModel.Hidden)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
new file mode 100644
index 0000000..ffbaa7f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.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.statusbar.chips.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+
+val Kosmos.screenRecordChipInteractor: FakeScreenRecordChipInteractor by
+ Kosmos.Fixture { FakeScreenRecordChipInteractor() }
+
+val Kosmos.callChipInteractor: FakeCallChipInteractor by Kosmos.Fixture { FakeCallChipInteractor() }
+
+val Kosmos.ongoingActivityChipsViewModel: OngoingActivityChipsViewModel by
+ Kosmos.Fixture {
+ OngoingActivityChipsViewModel(
+ testScope.backgroundScope,
+ screenRecordChipInteractor = screenRecordChipInteractor,
+ callChipInteractor = callChipInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index 6b27079..21d59f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -43,8 +43,6 @@
private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
private val lastAudibleVolumes: MutableMap<AudioStream, Int> = mutableMapOf()
- private var isAffectedByMute: MutableMap<AudioStream, Boolean> = mutableMapOf()
-
private fun getAudioStreamModelState(
audioStream: AudioStream
): MutableStateFlow<AudioStreamModel> =
@@ -55,6 +53,7 @@
volume = 0,
minVolume = 0,
maxVolume = 10,
+ isAffectedByMute = false,
isAffectedByRingerMode = false,
isMuted = false,
)
@@ -104,11 +103,4 @@
override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) {
mutableRingerMode.value = mode
}
-
- override suspend fun isAffectedByMute(audioStream: AudioStream): Boolean =
- isAffectedByMute[audioStream] ?: true
-
- fun setIsAffectedByMute(audioStream: AudioStream, isAffected: Boolean) {
- isAffectedByMute[audioStream] = isAffected
- }
}
diff --git a/ravenwood/OWNERS b/ravenwood/OWNERS
index 41fd68e..a90328c 100644
--- a/ravenwood/OWNERS
+++ b/ravenwood/OWNERS
@@ -2,4 +2,6 @@
jsharkey@google.com
omakoto@google.com
-jaggies@google.com
+
+per-file ravenwood-annotation-allowed-classes.txt = dplotnikov@google.com
+per-file texts/ravenwood-annotation-allowed-classes.txt = dplotnikov@google.com
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
index 3edca7e..01e90d8 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
@@ -18,6 +18,7 @@
import android.platform.test.annotations.DisabledOnNonRavenwood;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -43,5 +44,13 @@
Assert.assertTrue(RavenwoodRule.isOnRavenwood());
}
+ @Test
+ public void testDumpSystemProperties() {
+ Log.w("XXX", "System properties");
+ for (var sp : System.getProperties().entrySet()) {
+ Log.w("XXX", "" + sp.getKey() + "=" + sp.getValue());
+ }
+ }
+
// TODO: Add more tests
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 56a3c64..5506a46 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -100,10 +100,11 @@
android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
android.os.Binder.init$ravenwood();
- android.os.SystemProperties.init$ravenwood(
- rule.mSystemProperties.getValues(),
- rule.mSystemProperties.getKeyReadablePredicate(),
- rule.mSystemProperties.getKeyWritablePredicate());
+// android.os.SystemProperties.init$ravenwood(
+// rule.mSystemProperties.getValues(),
+// rule.mSystemProperties.getKeyReadablePredicate(),
+// rule.mSystemProperties.getKeyWritablePredicate());
+ setSystemProperties(rule.mSystemProperties);
ServiceManager.init$ravenwood();
LocalServices.removeAllServicesForTest();
@@ -157,7 +158,7 @@
LocalServices.removeAllServicesForTest();
ServiceManager.reset$ravenwood();
- android.os.SystemProperties.reset$ravenwood();
+ setSystemProperties(RavenwoodSystemProperties.DEFAULT_VALUES);
android.os.Binder.reset$ravenwood();
android.os.Process.reset$ravenwood();
@@ -291,4 +292,16 @@
collectMethods(clazz.getSuperclass(), result);
}
}
+
+ /**
+ * Set the current configuration to the actual SystemProperties.
+ */
+ public static void setSystemProperties(RavenwoodSystemProperties ravenwoodSystemProperties) {
+ var clone = new RavenwoodSystemProperties(ravenwoodSystemProperties, true);
+
+ android.os.SystemProperties.init$ravenwood(
+ clone.getValues(),
+ clone.getKeyReadablePredicate(),
+ clone.getKeyWritablePredicate());
+ }
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
index 85ad4e4..c3786ee 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -22,7 +22,9 @@
import java.util.Set;
import java.util.function.Predicate;
-class RavenwoodSystemProperties {
+public class RavenwoodSystemProperties {
+ private volatile boolean mIsImmutable;
+
private final Map<String, String> mValues = new HashMap<>();
/** Set of additional keys that should be considered readable */
@@ -101,15 +103,23 @@
setValue("ro.debuggable", "1");
}
- Map<String, String> getValues() {
+ /** Copy constructor */
+ public RavenwoodSystemProperties(RavenwoodSystemProperties source, boolean immutable) {
+ this.mKeyReadable.addAll(source.mKeyReadable);
+ this.mKeyWritable.addAll(source.mKeyWritable);
+ this.mValues.putAll(source.mValues);
+ this.mIsImmutable = immutable;
+ }
+
+ public Map<String, String> getValues() {
return new HashMap<>(mValues);
}
- Predicate<String> getKeyReadablePredicate() {
+ public Predicate<String> getKeyReadablePredicate() {
return mKeyReadablePredicate;
}
- Predicate<String> getKeyWritablePredicate() {
+ public Predicate<String> getKeyWritablePredicate() {
return mKeyWritablePredicate;
}
@@ -123,12 +133,20 @@
"vendor_dlkm",
};
+ private void ensureNotImmutable() {
+ if (mIsImmutable) {
+ throw new RuntimeException("Unable to update immutable instance");
+ }
+ }
+
/**
* Set the given property for all possible partitions where it could be defined. For
* example, the value of {@code ro.build.type} is typically also mirrored under
* {@code ro.system.build.type}, etc.
*/
private void setValueForPartitions(String key, String value) {
+ ensureNotImmutable();
+
setValue("ro." + key, value);
for (String partition : PARTITIONS) {
setValue("ro." + partition + "." + key, value);
@@ -136,6 +154,8 @@
}
public void setValue(String key, Object value) {
+ ensureNotImmutable();
+
final String valueString = (value == null) ? null : String.valueOf(value);
if ((valueString == null) || valueString.isEmpty()) {
mValues.remove(key);
@@ -145,16 +165,19 @@
}
public void setAccessNone(String key) {
+ ensureNotImmutable();
mKeyReadable.remove(key);
mKeyWritable.remove(key);
}
public void setAccessReadOnly(String key) {
+ ensureNotImmutable();
mKeyReadable.add(key);
mKeyWritable.remove(key);
}
public void setAccessReadWrite(String key) {
+ ensureNotImmutable();
mKeyReadable.add(key);
mKeyWritable.add(key);
}
@@ -172,4 +195,11 @@
return key;
}
}
-}
+
+ /**
+ * Return an immutable, default instance.
+ */
+ // Create a default instance, and make an immutable copy of it.
+ public static final RavenwoodSystemProperties DEFAULT_VALUES =
+ new RavenwoodSystemProperties(new RavenwoodSystemProperties(), true);
+}
\ No newline at end of file
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java
new file mode 100644
index 0000000..68bf922
--- /dev/null
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java
@@ -0,0 +1,57 @@
+/*
+ * 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.platform.test.ravenwood.nativesubstitution;
+
+import android.platform.test.ravenwood.RavenwoodSystemProperties;
+import android.util.Log;
+
+import com.android.internal.ravenwood.RavenwoodEnvironment;
+
+public class RavenwoodEnvironment_host {
+ private static final String TAG = RavenwoodEnvironment.TAG;
+
+ private static final Object sInitializeLock = new Object();
+
+ // @GuardedBy("sInitializeLock")
+ private static boolean sInitialized;
+
+ private RavenwoodEnvironment_host() {
+ }
+
+ /**
+ * Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}.
+ */
+ public static void ensureRavenwoodInitializedInternal() {
+ synchronized (sInitializeLock) {
+ if (sInitialized) {
+ return;
+ }
+ Log.w(TAG, "Initializing Ravenwood environment");
+
+ // Set the default values.
+ var sysProps = RavenwoodSystemProperties.DEFAULT_VALUES;
+
+ // We have a method that does it in RavenwoodRuleImpl, but we can't use that class
+ // here, So just inline it.
+ SystemProperties_host.initializeIfNeeded(
+ sysProps.getValues(),
+ sysProps.getKeyReadablePredicate(),
+ sysProps.getKeyWritablePredicate());
+
+ sInitialized = true;
+ }
+ }
+}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/SystemProperties_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/SystemProperties_host.java
index eba6c8b..e7479d3 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/SystemProperties_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/SystemProperties_host.java
@@ -47,6 +47,21 @@
@GuardedBy("sLock")
private static SparseArray<String> sKeyHandles = new SparseArray<>();
+ /**
+ * Basically the same as {@link #native_init$ravenwood}, but it'll only run if no values are
+ * set yet.
+ */
+ public static void initializeIfNeeded(Map<String, String> values,
+ Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate) {
+ synchronized (sLock) {
+ if (sValues != null) {
+ return; // Already initialized.
+ }
+ native_init$ravenwood(values, keyReadablePredicate, keyWritablePredicate,
+ () -> {});
+ }
+ }
+
public static void native_init$ravenwood(Map<String, String> values,
Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
Runnable changeCallback) {
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 0c3d40d..ba84485 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -23,13 +23,6 @@
}
flag {
- name: "ignore_view_state_reset_to_empty"
- namespace: "autofill"
- description: "Mitigation for view state reset to empty causing no save dialog to show issue"
- bug: "297976948"
-}
-
-flag {
name: "include_invisible_view_group_in_assist_structure"
namespace: "autofill"
description: "Mitigation for autofill providers miscalculating view visibility"
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index f8c4116..07daecd 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -1249,46 +1249,21 @@
return AUDIO_DEVICE_CATEGORY_UNKNOWN;
}
String deviceCategory = new String(deviceType);
-
- if (com.android.bluetooth.flags.Flags.supportMetadataDeviceTypesApis()) {
- switch (deviceCategory) {
- case DEVICE_TYPE_HEARING_AID:
- return AUDIO_DEVICE_CATEGORY_HEARING_AID;
- case DEVICE_TYPE_CARKIT:
- return AUDIO_DEVICE_CATEGORY_CARKIT;
- case DEVICE_TYPE_HEADSET:
- case DEVICE_TYPE_UNTETHERED_HEADSET:
- return AUDIO_DEVICE_CATEGORY_HEADPHONES;
- case DEVICE_TYPE_SPEAKER:
- return AUDIO_DEVICE_CATEGORY_SPEAKER;
- case DEVICE_TYPE_WATCH:
- return AUDIO_DEVICE_CATEGORY_WATCH;
- case DEVICE_TYPE_DEFAULT:
- // fall through
- default:
- break;
- }
- } else {
- // Duplicate switch for now to cover the cases when the flag is not rolled out
- // This will cover the cases in which clients could write directly to these
- // metadata keys
- switch (deviceCategory) {
- case "HearingAid":
- return AUDIO_DEVICE_CATEGORY_HEARING_AID;
- case "Carkit":
- return AUDIO_DEVICE_CATEGORY_CARKIT;
- case "Headset":
- case DEVICE_TYPE_UNTETHERED_HEADSET:
- return AUDIO_DEVICE_CATEGORY_HEADPHONES;
- case "Speaker":
- return AUDIO_DEVICE_CATEGORY_SPEAKER;
- case "Watch":
- return AUDIO_DEVICE_CATEGORY_WATCH;
- case "Default":
- // fall through
- default:
- break;
- }
+ switch (deviceCategory) {
+ case DEVICE_TYPE_HEARING_AID:
+ return AUDIO_DEVICE_CATEGORY_HEARING_AID;
+ case DEVICE_TYPE_CARKIT:
+ return AUDIO_DEVICE_CATEGORY_CARKIT;
+ case DEVICE_TYPE_HEADSET:
+ case DEVICE_TYPE_UNTETHERED_HEADSET:
+ return AUDIO_DEVICE_CATEGORY_HEADPHONES;
+ case DEVICE_TYPE_SPEAKER:
+ return AUDIO_DEVICE_CATEGORY_SPEAKER;
+ case DEVICE_TYPE_WATCH:
+ return AUDIO_DEVICE_CATEGORY_WATCH;
+ case DEVICE_TYPE_DEFAULT:
+ default:
+ // fall through
}
BluetoothClass deviceClass = device.getBluetoothClass();
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index b589f49..2fce295 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1446,6 +1446,7 @@
if (Flags.modesApi()) {
azr = new AutomaticZenRule.Builder(rule.name, rule.conditionId)
.setManualInvocationAllowed(rule.allowManualInvocation)
+ .setPackage(rule.pkg)
.setCreationTime(rule.creationTime)
.setIconResId(drawableResNameToResId(rule.pkg, rule.iconResName))
.setType(rule.type)
@@ -1464,8 +1465,8 @@
rule.conditionId, rule.zenPolicy,
NotificationManager.zenModeToInterruptionFilter(rule.zenMode),
rule.enabled, rule.creationTime);
+ azr.setPackageName(rule.pkg);
}
- azr.setPackageName(rule.pkg);
return azr;
}
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 24d4be8..a9450c4 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -290,7 +290,7 @@
}
// The insets position may be frozen by shouldFreezeInsetsPosition(), so refresh the
// position to the latest state when it is ready to show in new rotation.
- if (mTransitionOp == OP_APP_SWITCH) {
+ if (isSeamlessTransition()) {
for (int i = windowToken.getChildCount() - 1; i >= 0; i--) {
final WindowState w = windowToken.getChildAt(i);
final InsetsSourceProvider insetsProvider = w.getControllableInsetProvider();
@@ -506,11 +506,16 @@
boolean shouldFreezeInsetsPosition(WindowState w) {
// Non-change transition (OP_APP_SWITCH) and METHOD_BLAST don't use screenshot so the
// insets should keep original position before the start transaction is applied.
- return mTransitionOp != OP_LEGACY && (mTransitionOp == OP_APP_SWITCH
+ return mTransitionOp != OP_LEGACY && (isSeamlessTransition()
|| TransitionController.SYNC_METHOD == BLASTSyncEngine.METHOD_BLAST)
&& !mIsStartTransactionCommitted && canBeAsync(w.mToken) && isTargetToken(w.mToken);
}
+ /** Returns true if there won't be a screen rotation animation (screenshot-based). */
+ private boolean isSeamlessTransition() {
+ return mTransitionOp == OP_APP_SWITCH || mTransitionOp == OP_CHANGE_MAY_SEAMLESS;
+ }
+
/**
* Returns the transaction which will be applied after the window redraws in new rotation.
* This is used to update the position of insets animation leash synchronously.
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index f7910b0..bf8bf34 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -714,9 +714,6 @@
if (!state.hasRealCaller()) {
if (resultForCaller.allows()) {
- if (DEBUG_ACTIVITY_STARTS) {
- Slog.d(TAG, "Background activity start allowed. " + state);
- }
return allowBasedOnCaller(state);
}
return abortLaunch(state);
@@ -742,15 +739,9 @@
// Handle cases with explicit opt-in
if (resultForCaller.allows() && state.callerExplicitOptInOrAutoOptIn()) {
- if (DEBUG_ACTIVITY_STARTS) {
- Slog.d(TAG, "Activity start explicitly allowed by caller. " + state);
- }
return allowBasedOnCaller(state);
}
if (resultForRealCaller.allows() && state.realCallerExplicitOptInOrAutoOptIn()) {
- if (DEBUG_ACTIVITY_STARTS) {
- Slog.d(TAG, "Activity start explicitly allowed by real caller. " + state);
- }
return allowBasedOnRealCaller(state);
}
// Handle PendingIntent cases with default behavior next
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e49cb38..e2b0932 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5707,7 +5707,9 @@
// VR virtual display will be used to run and render 2D app within a VR experience.
&& mDisplayId != mWmService.mVr2dDisplayId
// Do not show system decorations on untrusted virtual display.
- && isTrusted();
+ && isTrusted()
+ // No system decoration on rear display.
+ && (mDisplay.getFlags() & Display.FLAG_REAR) == 0;
}
/**
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 72ae64c..e3827aa 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -239,8 +239,8 @@
dragSurface = mSurfaceControl;
}
}
- DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
- x, y, mThumbOffsetX, mThumbOffsetY, null, null, null, dragSurface, null,
+ DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, x, y,
+ mThumbOffsetX, mThumbOffsetY, mFlags, null, null, null, dragSurface, null,
mDragResult);
try {
if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DRAG_ENDED to " + ws);
@@ -298,7 +298,7 @@
* as a part of the dispatched event.
*/
private DragEvent createDropEvent(float x, float y, @Nullable WindowState touchedWin,
- boolean includeDragSurface) {
+ boolean includePrivateInfo) {
if (touchedWin != null) {
final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
final DragAndDropPermissionsHandler dragAndDropPermissions;
@@ -319,11 +319,16 @@
mData.fixUris(mSourceUserId);
}
}
+ final boolean targetInterceptsGlobalDrag = targetInterceptsGlobalDrag(touchedWin);
return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData,
- targetInterceptsGlobalDrag(touchedWin), dragAndDropPermissions);
+ /* includeDragSurface= */ targetInterceptsGlobalDrag,
+ /* includeDragFlags= */ targetInterceptsGlobalDrag,
+ dragAndDropPermissions);
} else {
return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData,
- includeDragSurface /* includeDragSurface */, null /* dragAndDropPermissions */);
+ /* includeDragSurface= */ includePrivateInfo,
+ /* includeDragFlags= */ includePrivateInfo,
+ null /* dragAndDropPermissions */);
}
}
@@ -525,7 +530,7 @@
ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null;
DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED,
newWin.translateToWindowX(touchX), newWin.translateToWindowY(touchY),
- data, false /* includeDragSurface */,
+ data, false /* includeDragSurface */, true /* includeDragFlags */,
null /* dragAndDropPermission */);
try {
newWin.mClient.dispatchDragEvent(event);
@@ -696,8 +701,10 @@
}
private DragEvent obtainDragEvent(int action, float x, float y, ClipData data,
- boolean includeDragSurface, IDragAndDropPermissions dragAndDropPermissions) {
+ boolean includeDragSurface, boolean includeDragFlags,
+ IDragAndDropPermissions dragAndDropPermissions) {
return DragEvent.obtain(action, x, y, mThumbOffsetX, mThumbOffsetY,
+ includeDragFlags ? mFlags : 0,
null /* localState */, mDataDescription, data,
includeDragSurface ? mSurfaceControl : null,
dragAndDropPermissions, false /* result */);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 8fb83fa..90c287c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -224,6 +224,7 @@
import android.view.InputChannel;
import android.view.InputWindowHandle;
import android.view.InsetsSource;
+import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.Surface;
import android.view.Surface.Rotation;
@@ -432,6 +433,10 @@
/** @see #isLastConfigReportedToClient() */
private boolean mLastConfigReportedToClient;
+ // TODO(b/339380439): Ensure to use the same object for IWindowSession#relayout
+ private final InsetsSourceControl.Array mLastReportedActiveControls =
+ new InsetsSourceControl.Array();
+
private final Configuration mTempConfiguration = new Configuration();
/**
@@ -3813,9 +3818,9 @@
}
final InsetsStateController stateController =
getDisplayContent().getInsetsStateController();
+ mLastReportedActiveControls.set(stateController.getControlsForDispatch(this));
try {
- mClient.insetsControlChanged(getCompatInsetsState(),
- stateController.getControlsForDispatch(this));
+ mClient.insetsControlChanged(getCompatInsetsState(), mLastReportedActiveControls);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver inset control state change to w=" + this, e);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NonRequiredPackageDeleteObserver.java b/services/devicepolicy/java/com/android/server/devicepolicy/NonRequiredPackageDeleteObserver.java
index 0e448cd..a1bf040 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NonRequiredPackageDeleteObserver.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NonRequiredPackageDeleteObserver.java
@@ -25,7 +25,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* Awaits the deletion of all the non-required apps.
@@ -33,38 +32,38 @@
final class NonRequiredPackageDeleteObserver extends IPackageDeleteObserver.Stub {
private static final int PACKAGE_DELETE_TIMEOUT_SEC = 30;
- private final AtomicInteger mPackageCount = new AtomicInteger(/* initialValue= */ 0);
private final CountDownLatch mLatch;
- private boolean mSuccess;
+ private boolean mFailed = false;
NonRequiredPackageDeleteObserver(int packageCount) {
this.mLatch = new CountDownLatch(packageCount);
- this.mPackageCount.set(packageCount);
}
@Override
public void packageDeleted(String packageName, int returnCode) {
if (returnCode != PackageManager.DELETE_SUCCEEDED) {
Slog.e(LOG_TAG, "Failed to delete package: " + packageName);
- mLatch.notifyAll();
- return;
- }
- int currentPackageCount = mPackageCount.decrementAndGet();
- if (currentPackageCount == 0) {
- mSuccess = true;
- Slog.i(LOG_TAG, "All non-required system apps with launcher icon, "
- + "and all disallowed apps have been uninstalled.");
+ mFailed = true;
}
mLatch.countDown();
}
public boolean awaitPackagesDeletion() {
try {
- mLatch.await(PACKAGE_DELETE_TIMEOUT_SEC, TimeUnit.SECONDS);
+ if (mLatch.await(PACKAGE_DELETE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ if (!mFailed) {
+ Slog.i(LOG_TAG, "All non-required system apps with launcher icon, "
+ + "and all disallowed apps have been uninstalled.");
+ }
+ return !mFailed;
+ } else {
+ Slog.i(LOG_TAG, "Waiting time elapsed before all package deletion finished");
+ return false;
+ }
} catch (InterruptedException e) {
Log.w(LOG_TAG, "Interrupted while waiting for package deletion", e);
Thread.currentThread().interrupt();
+ return false;
}
- return mSuccess;
}
}
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index a738acb..ea7bb8b 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -105,7 +105,6 @@
":PackageParserTestApp5",
":PackageParserTestApp6",
":PackageParserTestApp7",
- ":PackageParserTestApp8",
],
resource_zips: [":PackageManagerServiceServerTests_apks_as_resources"],
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index 5da202f..a0e0e1e 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -101,7 +101,6 @@
import com.android.internal.pm.pkg.component.ParsedUsesPermission;
import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
-import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.util.ArrayUtils;
import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.PackageInfoUtils;
@@ -127,7 +126,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -156,7 +154,6 @@
private static final String TEST_APP5_APK = "PackageParserTestApp5.apk";
private static final String TEST_APP6_APK = "PackageParserTestApp6.apk";
private static final String TEST_APP7_APK = "PackageParserTestApp7.apk";
- private static final String TEST_APP8_APK = "PackageParserTestApp8.apk";
private static final String PACKAGE_NAME = "com.android.servicestests.apps.packageparserapp";
@Before
@@ -817,39 +814,6 @@
}
}
- @Test
- @RequiresFlagsEnabled(android.content.res.Flags.FLAG_MANIFEST_FLAGGING)
- public void testParseWithFeatureFlagAttributes() throws Exception {
- final File testFile = extractFile(TEST_APP8_APK);
- try (PackageParser2 parser = new TestPackageParser2()) {
- Map<String, Boolean> flagValues = new HashMap<>();
- flagValues.put("my.flag1", true);
- flagValues.put("my.flag2", false);
- flagValues.put("my.flag3", false);
- flagValues.put("my.flag4", true);
- ParsingPackageUtils.getAconfigFlags().addFlagValuesForTesting(flagValues);
-
- // The manifest has:
- // <permission android:name="PERM1" android:featureFlag="my.flag1 " />
- // <permission android:name="PERM2" android:featureFlag=" !my.flag2" />
- // <permission android:name="PERM3" android:featureFlag="my.flag3" />
- // <permission android:name="PERM4" android:featureFlag="!my.flag4" />
- // <permission android:name="PERM5" android:featureFlag="unknown.flag" />
- // Therefore with the above flag values, only PERM1 and PERM2 should be present.
-
- final ParsedPackage pkg = parser.parsePackage(testFile, 0, false);
- List<String> permissionNames =
- pkg.getPermissions().stream().map(ParsedComponent::getName).toList();
- assertThat(permissionNames).contains(PACKAGE_NAME + ".PERM1");
- assertThat(permissionNames).contains(PACKAGE_NAME + ".PERM2");
- assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM3");
- assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM4");
- assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM5");
- } finally {
- testFile.delete();
- }
- }
-
/**
* A subclass of package parser that adds a "cache_" prefix to the package name for the cached
* results. This is used by tests to tell if a ParsedPackage is generated from the cache or not.
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
index 3def48a..131b380 100644
--- a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
+++ b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
@@ -116,20 +116,3 @@
resource_dirs: ["res"],
manifest: "AndroidManifestApp7.xml",
}
-
-android_test_helper_app {
- name: "PackageParserTestApp8",
- sdk_version: "current",
- srcs: ["**/*.java"],
- dex_preopt: {
- enabled: false,
- },
- optimize: {
- enabled: false,
- },
- resource_dirs: ["res"],
- aaptflags: [
- "--feature-flags my.flag1,my.flag2,my.flag3,my.flag4,unknown.flag",
- ],
- manifest: "AndroidManifestApp8.xml",
-}
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml
deleted file mode 100644
index d489c1b..0000000
--- a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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.servicestests.apps.packageparserapp" >
-
- <application>
- <activity android:name=".TestActivity"
- android:exported="true" />
- </application>
-
- <permission android:name="PERM1" android:featureFlag="my.flag1 " />
- <permission android:name="PERM2" android:featureFlag=" !my.flag2" />
- <permission android:name="PERM3" android:featureFlag="my.flag3" />
- <permission android:name="PERM4" android:featureFlag="!my.flag4" />
- <permission android:name="PERM5" android:featureFlag="unknown.flag" />
-</manifest>
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index a0461a6..7faf2aa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -324,6 +324,44 @@
});
}
+ @Test
+ public void testPrivateInterceptGlobalDragDropGetsDragFlags() {
+ mWindow.mAttrs.privateFlags |= PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
+ mWindow.setViewVisibility(View.GONE);
+
+ // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
+ // immediately after dispatching, which is a problem when using mockito arguments captor
+ // because it returns and modifies the same drag event
+ TestIWindow iwindow = (TestIWindow) mWindow.mClient;
+ final ArrayList<DragEvent> dragEvents = new ArrayList<>();
+ iwindow.setDragEventJournal(dragEvents);
+
+ startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG,
+ ClipData.newPlainText("label", "text"), () -> {
+ // Verify the start-drag event has the drag flags
+ final DragEvent dragEvent = dragEvents.get(0);
+ assertTrue(dragEvent.getAction() == ACTION_DRAG_STARTED);
+ assertTrue(dragEvent.getDragFlags() ==
+ (View.DRAG_FLAG_GLOBAL
+ | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG));
+
+ try {
+ mTarget.mDeferDragStateClosed = true;
+ mTarget.reportDropWindow(mWindow.mInputChannelToken, 0, 0);
+ // // Verify the drop event does not have the drag flags
+ mTarget.handleMotionEvent(false, 0, 0);
+ final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1);
+ assertTrue(dropEvent.getDragFlags() ==
+ (View.DRAG_FLAG_GLOBAL
+ | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG));
+
+ mTarget.reportDropResult(iwindow, true);
+ } finally {
+ mTarget.mDeferDragStateClosed = false;
+ }
+ });
+ }
+
private DragEvent last(ArrayList<DragEvent> list) {
return list.get(list.size() - 1);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 4fc222b..788b624 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -53,7 +53,7 @@
@Override
public void insetsControlChanged(InsetsState insetsState,
- InsetsSourceControl[] activeControls) {
+ InsetsSourceControl.Array activeControls) {
}
@Override
diff --git a/tools/hoststubgen/OWNERS b/tools/hoststubgen/OWNERS
index a8c5321..3d8888d 100644
--- a/tools/hoststubgen/OWNERS
+++ b/tools/hoststubgen/OWNERS
@@ -1,3 +1 @@
-omakoto@google.com
-jsharkey@google.com
-jaggies@google.com
+file:platform/frameworks/base:/ravenwood/OWNERS