Merge "Make SysUI Classic Feature Flag loading multi-user aware." into main
diff --git a/Android.bp b/Android.bp
index eabd9c7..cf73451 100644
--- a/Android.bp
+++ b/Android.bp
@@ -255,7 +255,7 @@
"android.hardware.vibrator-V1.1-java",
"android.hardware.vibrator-V1.2-java",
"android.hardware.vibrator-V1.3-java",
- "android.hardware.vibrator-V2-java",
+ "android.hardware.vibrator-V3-java",
"android.se.omapi-V1-java",
"android.system.suspend.control.internal-java",
"devicepolicyprotosnano",
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 36a335e..fd0262e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -409,6 +409,7 @@
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String USE_ON_DEVICE_INTELLIGENCE = "android.permission.USE_ON_DEVICE_INTELLIGENCE";
field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED";
+ field @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public static final String VIBRATE_VENDOR_EFFECTS = "android.permission.VIBRATE_VENDOR_EFFECTS";
field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
field public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
@@ -11354,6 +11355,10 @@
field @NonNull public static final android.os.Parcelable.Creator<android.os.UserManager.EnforcingUser> CREATOR;
}
+ public abstract class VibrationEffect implements android.os.Parcelable {
+ method @FlaggedApi("android.os.vibrator.vendor_vibration_effects") @NonNull @RequiresPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS) public static android.os.VibrationEffect createVendorEffect(@NonNull android.os.PersistableBundle);
+ }
+
public abstract class Vibrator {
method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 88b5275..90af259 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2577,6 +2577,16 @@
public static final class VibrationEffect.Composition.UnreachableAfterRepeatingIndefinitelyException extends java.lang.IllegalStateException {
}
+ @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public static final class VibrationEffect.VendorEffect extends android.os.VibrationEffect {
+ method @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull();
+ method public long getDuration();
+ method public int getEffectStrength();
+ method public float getLinearScale();
+ method @NonNull public android.os.PersistableBundle getVendorData();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.VendorEffect> CREATOR;
+ }
+
public static class VibrationEffect.VibrationParameter {
method @NonNull public static android.os.VibrationEffect.VibrationParameter targetAmplitude(@FloatRange(from=0, to=1) float);
method @NonNull public static android.os.VibrationEffect.VibrationParameter targetFrequency(@FloatRange(from=1) float);
diff --git a/core/java/android/app/servertransaction/BaseClientRequest.java b/core/java/android/app/servertransaction/BaseClientRequest.java
index f275175..bcbb514 100644
--- a/core/java/android/app/servertransaction/BaseClientRequest.java
+++ b/core/java/android/app/servertransaction/BaseClientRequest.java
@@ -22,31 +22,34 @@
/**
* Base interface for individual requests from server to client.
* Each of them can be prepared before scheduling and, eventually, executed.
+ *
* @hide
*/
-public interface BaseClientRequest extends ObjectPoolItem {
+public interface BaseClientRequest {
/**
* Prepares the client request before scheduling.
* An example of this might be informing about pending updates for some values.
*
- * @param client Target client handler.
+ * @param client target client handler.
*/
default void preExecute(@NonNull ClientTransactionHandler client) {
}
/**
* Executes the request.
- * @param client Target client handler.
- * @param pendingActions Container that may have data pending to be used.
+ *
+ * @param client target client handler.
+ * @param pendingActions container that may have data pending to be used.
*/
void execute(@NonNull ClientTransactionHandler client,
@NonNull PendingTransactionActions pendingActions);
/**
* Performs all actions that need to happen after execution, e.g. report the result to server.
- * @param client Target client handler.
- * @param pendingActions Container that may have data pending to be used.
+ *
+ * @param client target client handler.
+ * @param pendingActions container that may have data pending to be used.
*/
default void postExecute(@NonNull ClientTransactionHandler client,
@NonNull PendingTransactionActions pendingActions) {
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index 2d35bf1..99ebe1b 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -75,17 +75,6 @@
pw.append(prefix).println(this);
}
- /**
- * Provides a default empty implementation for progressive cleanup.
- *
- * @deprecated This method is deprecated. The object pool is no longer used, so there's
- * no need to recycle objects.
- * TODO(b/311089192): Remove once ObjectPoolItem inheritance is removed.
- */
- @Override
- @Deprecated
- public void recycle() {}
-
// Parcelable
@Override
diff --git a/core/java/android/app/servertransaction/ObjectPool.java b/core/java/android/app/servertransaction/ObjectPool.java
deleted file mode 100644
index e86ca37..0000000
--- a/core/java/android/app/servertransaction/ObjectPool.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.servertransaction;
-
-/**
- * An object pool that can provide reused objects if available.
- *
- * @hide
- * @deprecated This class is deprecated. Directly create new instances of objects instead of
- * obtaining them from this pool.
- * TODO(b/311089192): Clean up usages of the pool.
- */
-@Deprecated
-class ObjectPool {
-
- /**
- * Obtain an instance of a specific class from the pool
- *
- * @param ignoredItemClass The class of the object we're looking for.
- * @return An instance or null if there is none.
- * @deprecated This method is deprecated. Directly create new instances of objects instead of
- * obtaining them from this pool.
- */
- @Deprecated
- public static <T extends ObjectPoolItem> T obtain(Class<T> ignoredItemClass) {
- return null;
- }
-
- /**
- * Recycle the object to the pool. The object should be properly cleared before this.
- *
- * @param ignoredItem The object to recycle.
- * @see ObjectPoolItem#recycle()
- * @deprecated This method is deprecated. The object pool is no longer used, so there's
- * no need to recycle objects.
- */
- @Deprecated
- public static <T extends ObjectPoolItem> void recycle(T ignoredItem) {
- }
-}
diff --git a/core/java/android/app/servertransaction/ObjectPoolItem.java b/core/java/android/app/servertransaction/ObjectPoolItem.java
deleted file mode 100644
index 0141f6e..0000000
--- a/core/java/android/app/servertransaction/ObjectPoolItem.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.servertransaction;
-
-/**
- * Base interface for all lifecycle items that can be put in object pool.
- *
- * @hide
- * @deprecated This interface is deprecated. Objects should no longer be pooled.
- * TODO(b/311089192): Clean up usages of this interface.
- */
-@Deprecated
-public interface ObjectPoolItem {
- /**
- * Clear the contents of the item and putting it to a pool. The implementation should call
- * {@link ObjectPool#recycle(ObjectPoolItem)} passing itself.
- *
- * @deprecated This method is deprecated. The object pool is no longer used, so there's
- * no need to recycle objects.
- */
- @Deprecated
- void recycle();
-}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 68012e2..3a23e6b 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -45,6 +45,7 @@
/**
* Class that manages transaction execution in the correct order.
+ *
* @hide
*/
public class TransactionExecutor {
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index c44ada7..785fa59 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -55,7 +55,7 @@
// Temp holder for lifecycle path.
// No direct transition between two states should take more than one complete cycle of 6 states.
@ActivityLifecycleItem.LifecycleState
- private IntArray mLifecycleSequence = new IntArray(6);
+ private final IntArray mLifecycleSequence = new IntArray(6 /* initialCapacity */);
/**
* Calculate the path through main lifecycle states for an activity and fill
diff --git a/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java b/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java
index 1bea468..76b39d5 100644
--- a/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java
+++ b/core/java/android/app/servertransaction/WindowContextWindowRemovalItem.java
@@ -28,12 +28,17 @@
/**
* {@link android.window.WindowContext} window removal message.
+ *
* @hide
*/
public class WindowContextWindowRemovalItem extends ClientTransactionItem {
- @Nullable
- private IBinder mClientToken;
+ @NonNull
+ private final IBinder mClientToken;
+
+ public WindowContextWindowRemovalItem(@NonNull IBinder clientToken) {
+ mClientToken = requireNonNull(clientToken);
+ }
@Override
public void execute(@NonNull ClientTransactionHandler client,
@@ -41,28 +46,6 @@
client.handleWindowContextWindowRemoval(mClientToken);
}
- // ObjectPoolItem implementation
-
- private WindowContextWindowRemovalItem() {}
-
- /** Obtains an instance initialized with provided params. */
- public static WindowContextWindowRemovalItem obtain(@NonNull IBinder clientToken) {
- WindowContextWindowRemovalItem instance =
- ObjectPool.obtain(WindowContextWindowRemovalItem.class);
- if (instance == null) {
- instance = new WindowContextWindowRemovalItem();
- }
- instance.mClientToken = requireNonNull(clientToken);
-
- return instance;
- }
-
- @Override
- public void recycle() {
- mClientToken = null;
- ObjectPool.recycle(this);
- }
-
// Parcelable implementation
/** Writes to Parcel. */
diff --git a/core/java/android/os/CombinedVibration.java b/core/java/android/os/CombinedVibration.java
index f32a1f8..77d6cb7 100644
--- a/core/java/android/os/CombinedVibration.java
+++ b/core/java/android/os/CombinedVibration.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.TestApi;
+import android.os.vibrator.Flags;
import android.util.SparseArray;
import com.android.internal.util.Preconditions;
@@ -152,6 +153,9 @@
/** @hide */
public abstract boolean hasVibrator(int vibratorId);
+ /** @hide */
+ public abstract boolean hasVendorEffects();
+
/**
* Returns a compact version of the {@link #toString()} result for debugging purposes.
*
@@ -424,6 +428,15 @@
return true;
}
+ /** @hide */
+ @Override
+ public boolean hasVendorEffects() {
+ if (!Flags.vendorVibrationEffects()) {
+ return false;
+ }
+ return mEffect instanceof VibrationEffect.VendorEffect;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -605,6 +618,20 @@
return mEffects.indexOfKey(vibratorId) >= 0;
}
+ /** @hide */
+ @Override
+ public boolean hasVendorEffects() {
+ if (!Flags.vendorVibrationEffects()) {
+ return false;
+ }
+ for (int i = 0; i < mEffects.size(); i++) {
+ if (mEffects.get(i) instanceof VibrationEffect.VendorEffect) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -838,6 +865,17 @@
return false;
}
+ /** @hide */
+ @Override
+ public boolean hasVendorEffects() {
+ for (int i = 0; i < mEffects.size(); i++) {
+ if (mEffects.get(i).hasVendorEffects()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index efbd96b..44edf29 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -16,18 +16,25 @@
package android.os;
+import static android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS;
+
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
+import android.hardware.vibrator.IVibrator;
import android.hardware.vibrator.V1_0.EffectStrength;
import android.hardware.vibrator.V1_3.Effect;
import android.net.Uri;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
@@ -46,6 +53,7 @@
import java.util.Locale;
import java.util.Objects;
import java.util.StringJoiner;
+import java.util.function.BiFunction;
/**
* A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
@@ -53,6 +61,9 @@
* <p>These effects may be any number of things, from single shot vibrations to complex waveforms.
*/
public abstract class VibrationEffect implements Parcelable {
+ private static final int PARCEL_TOKEN_COMPOSED = 1;
+ private static final int PARCEL_TOKEN_VENDOR_EFFECT = 2;
+
// Stevens' coefficient to scale the perceived vibration intensity.
private static final float SCALE_GAMMA = 0.65f;
// If a vibration is playing for longer than 1s, it's probably not haptic feedback
@@ -316,6 +327,28 @@
}
/**
+ * Create a vendor-defined vibration effect.
+ *
+ * <p>Vendor effects offer more flexibility for accessing vendor-specific vibrator capabilities,
+ * enabling control over any vibration parameter and more generic vibration waveforms for apps
+ * provided by the device vendor.
+ *
+ * <p>This requires hardware-specific implementation of the effect and will not have any
+ * platform fallback support.
+ *
+ * @param effect An opaque representation of the vibration effect which can also be serialized.
+ * @return The desired effect.
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @FlaggedApi(FLAG_VENDOR_VIBRATION_EFFECTS)
+ @RequiresPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)
+ public static VibrationEffect createVendorEffect(@NonNull PersistableBundle effect) {
+ return new VendorEffect(effect, VendorEffect.DEFAULT_STRENGTH, VendorEffect.DEFAULT_SCALE);
+ }
+
+ /**
* Get a predefined vibration effect.
*
* <p>Predefined effects are a set of common vibration effects that should be identical,
@@ -508,7 +541,7 @@
* Gets the estimated duration of the vibration in milliseconds.
*
* <p>For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
- * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
+ * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. predefined effects where
* the length is device and potentially run-time dependent), this returns -1.
*
* @hide
@@ -550,7 +583,19 @@
*
* @hide
*/
- public abstract <T extends VibrationEffect> T resolve(int defaultAmplitude);
+ @NonNull
+ public abstract VibrationEffect resolve(int defaultAmplitude);
+
+ /**
+ * Applies given effect strength to predefined and vendor-specific effects.
+ *
+ * @param effectStrength new effect strength to be applied, one of
+ * VibrationEffect.EFFECT_STRENGTH_*.
+ * @return this if there is no change, or a copy of this effect with new strength otherwise
+ * @hide
+ */
+ @NonNull
+ public abstract VibrationEffect applyEffectStrength(int effectStrength);
/**
* Scale the vibration effect intensity with the given constraints.
@@ -562,7 +607,20 @@
*
* @hide
*/
- public abstract <T extends VibrationEffect> T scale(float scaleFactor);
+ @NonNull
+ public abstract VibrationEffect scale(float scaleFactor);
+
+ /**
+ * Performs a linear scaling on the effect intensity with the given factor.
+ *
+ * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+ * scale down the intensity, values larger than 1 will scale up
+ * @return this if there is no scaling to be done, or a copy of this effect with scaled
+ * vibration intensity otherwise
+ * @hide
+ */
+ @NonNull
+ public abstract VibrationEffect scaleLinearly(float scaleFactor);
/**
* Ensures that the effect is repeating indefinitely or not. This is a lossy operation and
@@ -651,38 +709,26 @@
/** @hide */
public static String effectIdToString(int effectId) {
- switch (effectId) {
- case EFFECT_CLICK:
- return "CLICK";
- case EFFECT_TICK:
- return "TICK";
- case EFFECT_HEAVY_CLICK:
- return "HEAVY_CLICK";
- case EFFECT_DOUBLE_CLICK:
- return "DOUBLE_CLICK";
- case EFFECT_POP:
- return "POP";
- case EFFECT_THUD:
- return "THUD";
- case EFFECT_TEXTURE_TICK:
- return "TEXTURE_TICK";
- default:
- return Integer.toString(effectId);
- }
+ return switch (effectId) {
+ case EFFECT_CLICK -> "CLICK";
+ case EFFECT_TICK -> "TICK";
+ case EFFECT_HEAVY_CLICK -> "HEAVY_CLICK";
+ case EFFECT_DOUBLE_CLICK -> "DOUBLE_CLICK";
+ case EFFECT_POP -> "POP";
+ case EFFECT_THUD -> "THUD";
+ case EFFECT_TEXTURE_TICK -> "TEXTURE_TICK";
+ default -> Integer.toString(effectId);
+ };
}
/** @hide */
public static String effectStrengthToString(int effectStrength) {
- switch (effectStrength) {
- case EFFECT_STRENGTH_LIGHT:
- return "LIGHT";
- case EFFECT_STRENGTH_MEDIUM:
- return "MEDIUM";
- case EFFECT_STRENGTH_STRONG:
- return "STRONG";
- default:
- return Integer.toString(effectStrength);
- }
+ return switch (effectStrength) {
+ case EFFECT_STRENGTH_LIGHT -> "LIGHT";
+ case EFFECT_STRENGTH_MEDIUM -> "MEDIUM";
+ case EFFECT_STRENGTH_STRONG -> "STRONG";
+ default -> Integer.toString(effectStrength);
+ };
}
/**
@@ -712,12 +758,15 @@
private final ArrayList<VibrationEffectSegment> mSegments;
private final int mRepeatIndex;
+ /** @hide */
Composed(@NonNull Parcel in) {
- this(in.readArrayList(
- VibrationEffectSegment.class.getClassLoader(), VibrationEffectSegment.class),
+ this(Objects.requireNonNull(in.readArrayList(
+ VibrationEffectSegment.class.getClassLoader(),
+ VibrationEffectSegment.class)),
in.readInt());
}
+ /** @hide */
Composed(@NonNull VibrationEffectSegment segment) {
this(Arrays.asList(segment), /* repeatIndex= */ -1);
}
@@ -844,7 +893,7 @@
}
int segmentCount = mSegments.size();
if (segmentCount > MAX_HAPTIC_FEEDBACK_COMPOSITION_SIZE) {
- // Vibration has some prebaked or primitive constants, it should be limited to the
+ // Vibration has some predefined or primitive constants, it should be limited to the
// max composition size used to classify haptic feedbacks.
return false;
}
@@ -867,34 +916,28 @@
@NonNull
@Override
public Composed resolve(int defaultAmplitude) {
- int segmentCount = mSegments.size();
- ArrayList<VibrationEffectSegment> resolvedSegments = new ArrayList<>(segmentCount);
- for (int i = 0; i < segmentCount; i++) {
- resolvedSegments.add(mSegments.get(i).resolve(defaultAmplitude));
- }
- if (resolvedSegments.equals(mSegments)) {
- return this;
- }
- Composed resolved = new Composed(resolvedSegments, mRepeatIndex);
- resolved.validate();
- return resolved;
+ return applyToSegments(VibrationEffectSegment::resolve, defaultAmplitude);
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public VibrationEffect applyEffectStrength(int effectStrength) {
+ return applyToSegments(VibrationEffectSegment::applyEffectStrength, effectStrength);
}
/** @hide */
@NonNull
@Override
public Composed scale(float scaleFactor) {
- int segmentCount = mSegments.size();
- ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
- for (int i = 0; i < segmentCount; i++) {
- scaledSegments.add(mSegments.get(i).scale(scaleFactor));
- }
- if (scaledSegments.equals(mSegments)) {
- return this;
- }
- Composed scaled = new Composed(scaledSegments, mRepeatIndex);
- scaled.validate();
- return scaled;
+ return applyToSegments(VibrationEffectSegment::scale, scaleFactor);
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public Composed scaleLinearly(float scaleFactor) {
+ return applyToSegments(VibrationEffectSegment::scaleLinearly, scaleFactor);
}
/** @hide */
@@ -926,10 +969,9 @@
if (this == o) {
return true;
}
- if (!(o instanceof Composed)) {
+ if (!(o instanceof Composed other)) {
return false;
}
- Composed other = (Composed) o;
return mSegments.equals(other.mSegments) && mRepeatIndex == other.mRepeatIndex;
}
@@ -969,6 +1011,7 @@
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_COMPOSED);
out.writeList(mSegments);
out.writeInt(mRepeatIndex);
}
@@ -1011,6 +1054,208 @@
return stepSegment;
}
+
+ private <T> Composed applyToSegments(
+ BiFunction<VibrationEffectSegment, T, VibrationEffectSegment> function, T param) {
+ int segmentCount = mSegments.size();
+ ArrayList<VibrationEffectSegment> updatedSegments = new ArrayList<>(segmentCount);
+ for (int i = 0; i < segmentCount; i++) {
+ updatedSegments.add(function.apply(mSegments.get(i), param));
+ }
+ if (mSegments.equals(updatedSegments)) {
+ return this;
+ }
+ Composed updated = new Composed(updatedSegments, mRepeatIndex);
+ updated.validate();
+ return updated;
+ }
+ }
+
+ /**
+ * Implementation of {@link VibrationEffect} described by a generic {@link PersistableBundle}
+ * defined by vendors.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(FLAG_VENDOR_VIBRATION_EFFECTS)
+ public static final class VendorEffect extends VibrationEffect {
+ /** @hide */
+ public static final int DEFAULT_STRENGTH = VibrationEffect.EFFECT_STRENGTH_MEDIUM;
+ /** @hide */
+ public static final float DEFAULT_SCALE = 1.0f;
+
+ private final PersistableBundle mVendorData;
+ private final int mEffectStrength;
+ private final float mLinearScale;
+
+ /** @hide */
+ VendorEffect(@NonNull Parcel in) {
+ this(Objects.requireNonNull(
+ in.readPersistableBundle(VibrationEffect.class.getClassLoader())),
+ in.readInt(), in.readFloat());
+ }
+
+ /** @hide */
+ public VendorEffect(@NonNull PersistableBundle vendorData, int effectStrength,
+ float linearScale) {
+ mVendorData = vendorData;
+ mEffectStrength = effectStrength;
+ mLinearScale = linearScale;
+ }
+
+ @NonNull
+ public PersistableBundle getVendorData() {
+ return mVendorData;
+ }
+
+ public int getEffectStrength() {
+ return mEffectStrength;
+ }
+
+ public float getLinearScale() {
+ return mLinearScale;
+ }
+
+ /** @hide */
+ @Override
+ @Nullable
+ public long[] computeCreateWaveformOffOnTimingsOrNull() {
+ return null;
+ }
+
+ /** @hide */
+ @Override
+ public void validate() {
+ Preconditions.checkArgument(!mVendorData.isEmpty(),
+ "Vendor effect bundle must be non-empty");
+ }
+
+ @Override
+ public long getDuration() {
+ return -1; // UNKNOWN
+ }
+
+ /** @hide */
+ @Override
+ public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
+ return vibratorInfo.hasCapability(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+ }
+
+ /** @hide */
+ @Override
+ public boolean isHapticFeedbackCandidate() {
+ return false;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public VendorEffect resolve(int defaultAmplitude) {
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public VibrationEffect applyEffectStrength(int effectStrength) {
+ if (mEffectStrength == effectStrength) {
+ return this;
+ }
+ VendorEffect updated = new VendorEffect(mVendorData, effectStrength, mLinearScale);
+ updated.validate();
+ return updated;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public VendorEffect scale(float scaleFactor) {
+ // Vendor effect strength cannot be scaled with this method.
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public VibrationEffect scaleLinearly(float scaleFactor) {
+ if (Float.compare(mLinearScale, scaleFactor) == 0) {
+ return this;
+ }
+ VendorEffect updated = new VendorEffect(mVendorData, mEffectStrength, scaleFactor);
+ updated.validate();
+ return updated;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public VendorEffect applyRepeatingIndefinitely(boolean wantRepeating, int loopDelayMs) {
+ return this;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof VendorEffect other)) {
+ return false;
+ }
+ return mEffectStrength == other.mEffectStrength
+ && (Float.compare(mLinearScale, other.mLinearScale) == 0)
+ // Make sure it calls unparcel for both before calling BaseBundle.kindofEquals.
+ && mVendorData.size() == other.mVendorData.size()
+ && BaseBundle.kindofEquals(mVendorData, other.mVendorData);
+ }
+
+ @Override
+ public int hashCode() {
+ // PersistableBundle does not implement hashCode, so use its size as a shortcut.
+ return Objects.hash(mVendorData.size(), mEffectStrength, mLinearScale);
+ }
+
+ @Override
+ public String toString() {
+ return String.format(Locale.ROOT,
+ "VendorEffect{vendorData=%s, strength=%s, scale=%.2f}",
+ mVendorData, effectStrengthToString(mEffectStrength), mLinearScale);
+ }
+
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ return String.format(Locale.ROOT, "vendorEffect=%s, strength=%s, scale=%.2f",
+ mVendorData.toShortString(), effectStrengthToString(mEffectStrength),
+ mLinearScale);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_VENDOR_EFFECT);
+ out.writePersistableBundle(mVendorData);
+ out.writeInt(mEffectStrength);
+ out.writeFloat(mLinearScale);
+ }
+
+ @NonNull
+ public static final Creator<VendorEffect> CREATOR =
+ new Creator<VendorEffect>() {
+ @Override
+ public VendorEffect createFromParcel(Parcel in) {
+ return new VendorEffect(in);
+ }
+
+ @Override
+ public VendorEffect[] newArray(int size) {
+ return new VendorEffect[size];
+ }
+ };
}
/**
@@ -1249,7 +1494,9 @@
if (mRepeatIndex >= 0) {
throw new UnreachableAfterRepeatingIndefinitelyException();
}
- Composed composed = (Composed) effect;
+ if (!(effect instanceof Composed composed)) {
+ throw new IllegalArgumentException("Can't add vendor effects to composition.");
+ }
if (composed.getRepeatIndex() >= 0) {
// Start repeating from the index relative to the composed waveform.
mRepeatIndex = mSegments.size() + composed.getRepeatIndex();
@@ -1285,28 +1532,18 @@
* @hide
*/
public static String primitiveToString(@PrimitiveType int id) {
- switch (id) {
- case PRIMITIVE_NOOP:
- return "NOOP";
- case PRIMITIVE_CLICK:
- return "CLICK";
- case PRIMITIVE_THUD:
- return "THUD";
- case PRIMITIVE_SPIN:
- return "SPIN";
- case PRIMITIVE_QUICK_RISE:
- return "QUICK_RISE";
- case PRIMITIVE_SLOW_RISE:
- return "SLOW_RISE";
- case PRIMITIVE_QUICK_FALL:
- return "QUICK_FALL";
- case PRIMITIVE_TICK:
- return "TICK";
- case PRIMITIVE_LOW_TICK:
- return "LOW_TICK";
- default:
- return Integer.toString(id);
- }
+ return switch (id) {
+ case PRIMITIVE_NOOP -> "NOOP";
+ case PRIMITIVE_CLICK -> "CLICK";
+ case PRIMITIVE_THUD -> "THUD";
+ case PRIMITIVE_SPIN -> "SPIN";
+ case PRIMITIVE_QUICK_RISE -> "QUICK_RISE";
+ case PRIMITIVE_SLOW_RISE -> "SLOW_RISE";
+ case PRIMITIVE_QUICK_FALL -> "QUICK_FALL";
+ case PRIMITIVE_TICK -> "TICK";
+ case PRIMITIVE_LOW_TICK -> "LOW_TICK";
+ default -> Integer.toString(id);
+ };
}
}
@@ -1640,7 +1877,17 @@
new Parcelable.Creator<VibrationEffect>() {
@Override
public VibrationEffect createFromParcel(Parcel in) {
- return new Composed(in);
+ switch (in.readInt()) {
+ case PARCEL_TOKEN_COMPOSED:
+ return new Composed(in);
+ case PARCEL_TOKEN_VENDOR_EFFECT:
+ if (Flags.vendorVibrationEffects()) {
+ return new VendorEffect(in);
+ } // else fall through
+ default:
+ throw new IllegalStateException(
+ "Unexpected vibration effect type token in parcel.");
+ }
}
@Override
public VibrationEffect[] newArray(int size) {
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index ad2f59d..f4e2a7e 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -53,3 +53,14 @@
purpose: PURPOSE_FEATURE
}
}
+
+flag {
+ namespace: "haptics"
+ name: "vendor_vibration_effects"
+ is_exported: true
+ description: "Enabled System APIs for vendor-defined vibration effects"
+ bug: "345454923"
+ metadata {
+ purpose: PURPOSE_FEATURE
+ }
+}
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index 1a660be..3b25109 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -477,7 +477,7 @@
} else if (mMinuteSpinnerInput.hasFocus()) {
inputMethodManager.hideSoftInputFromView(mMinuteSpinnerInput, 0);
mMinuteSpinnerInput.clearFocus();
- } else if (mAmPmSpinnerInput.hasFocus()) {
+ } else if (mAmPmSpinnerInput != null && mAmPmSpinnerInput.hasFocus()) {
inputMethodManager.hideSoftInputFromView(mAmPmSpinnerInput, 0);
mAmPmSpinnerInput.clearFocus();
}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 7f48c42..76989f9 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -180,6 +180,17 @@
}
flag {
+ name: "use_tasks_dim_only"
+ namespace: "windowing_frontend"
+ description: "Only use the task's dim and reparent it to the display area when needed instead of coordinating multiple dimmers"
+ bug: "352522056"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "release_snapshot_aggressively"
namespace: "windowing_frontend"
description: "Actively release task snapshot memory"
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index bf5df03..53ef49b 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -317,6 +317,7 @@
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, name, name, (int) mBeginVsyncId);
markEvent("FT#beginVsync", mBeginVsyncId);
markEvent("FT#layerId", mSurfaceControl.getLayerId());
+ markCujUiThread();
mJankDataListenerRegistration =
mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
if (!mSurfaceOnly) {
@@ -433,6 +434,13 @@
}
}
+ private void markCujUiThread() {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+ // This is being called from the CUJ ui thread.
+ Trace.instant(Trace.TRACE_TAG_APP, mConfig.getSessionName() + "#UIThread");
+ }
+ }
+
private void notifyCujEvent(String action, @Reasons int reason) {
if (mListener == null) return;
mListener.onCujEvents(this, action, reason);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 193836e..f3dac23 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2611,6 +2611,14 @@
<permission android:name="android.permission.VIBRATE_SYSTEM_CONSTANTS"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows access to perform vendor effects in the vibrator.
+ <p>Protection level: signature
+ @FlaggedApi("android.os.vibrator.vendor_vibration_effects")
+ @hide
+ -->
+ <permission android:name="android.permission.VIBRATE_VENDOR_EFFECTS"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi Allows access to the vibrator state.
<p>Protection level: signature
@hide
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index 96d4cf4..f023196 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -185,8 +185,8 @@
@Test
public void testWindowContextWindowRemovalItem_execute() {
- final WindowContextWindowRemovalItem item = WindowContextWindowRemovalItem.obtain(
- mWindowClientToken);
+ final WindowContextWindowRemovalItem item =
+ new WindowContextWindowRemovalItem(mWindowClientToken);
item.execute(mHandler, mPendingActions);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index c2d56b6..76a53d4 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -525,10 +525,6 @@
}
@Override
- public void recycle() {
- }
-
- @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
}
diff --git a/core/tests/vibrator/Android.bp b/core/tests/vibrator/Android.bp
index 3ebe150..920ab59 100644
--- a/core/tests/vibrator/Android.bp
+++ b/core/tests/vibrator/Android.bp
@@ -18,6 +18,7 @@
"androidx.test.ext.junit",
"androidx.test.runner",
"androidx.test.rules",
+ "flag-junit",
"mockito-target-minus-junit4",
"truth",
"testng",
diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
index e875875..098ade4 100644
--- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java
+++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
@@ -20,6 +20,8 @@
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -29,6 +31,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertNotEquals;
import static org.testng.Assert.assertThrows;
import android.content.ContentInterface;
@@ -38,8 +41,12 @@
import android.hardware.vibrator.IVibrator;
import android.net.Uri;
import android.os.VibrationEffect.Composition.UnreachableAfterRepeatingIndefinitelyException;
+import android.os.vibrator.Flags;
+import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import com.android.internal.R;
@@ -284,10 +291,13 @@
}
@Test
- public void computeLegacyPattern_notPatternPased() {
- VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
-
- assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+ public void computeLegacyPattern_notPatternBased() {
+ assertNull(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
+ .computeCreateWaveformOffOnTimingsOrNull());
+ if (Flags.vendorVibrationEffects()) {
+ assertNull(VibrationEffect.createVendorEffect(createNonEmptyBundle())
+ .computeCreateWaveformOffOnTimingsOrNull());
+ }
}
@Test
@@ -472,6 +482,18 @@
}
@Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void testValidateVendorEffect() {
+ PersistableBundle vendorData = new PersistableBundle();
+ vendorData.putInt("key", 1);
+ VibrationEffect.createVendorEffect(vendorData).validate();
+
+ PersistableBundle emptyData = new PersistableBundle();
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createVendorEffect(emptyData).validate());
+ }
+
+ @Test
public void testValidateWaveform() {
VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1).validate();
VibrationEffect.createWaveform(new long[]{10, 10}, new int[] {0, 0}, -1).validate();
@@ -634,16 +656,16 @@
@Test
public void testResolveOneShot() {
- VibrationEffect.Composed resolved = DEFAULT_ONE_SHOT.resolve(51);
- assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude());
+ VibrationEffect resolved = DEFAULT_ONE_SHOT.resolve(51);
+ assertEquals(0.2f, getStepSegment(resolved, 0).getAmplitude());
assertThrows(IllegalArgumentException.class, () -> DEFAULT_ONE_SHOT.resolve(1000));
}
@Test
public void testResolveWaveform() {
- VibrationEffect.Composed resolved = TEST_WAVEFORM.resolve(102);
- assertEquals(0.4f, ((StepSegment) resolved.getSegments().get(2)).getAmplitude());
+ VibrationEffect resolved = TEST_WAVEFORM.resolve(102);
+ assertEquals(0.4f, getStepSegment(resolved, 2).getAmplitude());
assertThrows(IllegalArgumentException.class, () -> TEST_WAVEFORM.resolve(1000));
}
@@ -655,63 +677,127 @@
}
@Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void testResolveVendorEffect() {
+ VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle());
+ assertEquals(effect, effect.resolve(51));
+ }
+
+ @Test
public void testResolveComposed() {
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1)
.compose();
assertEquals(effect, effect.resolve(51));
- VibrationEffect.Composed resolved = VibrationEffect.startComposition()
+ VibrationEffect resolved = VibrationEffect.startComposition()
.addEffect(DEFAULT_ONE_SHOT)
.compose()
.resolve(51);
- assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude());
+ assertEquals(0.2f, getStepSegment(resolved, 0).getAmplitude());
}
@Test
public void testScaleOneShot() {
- VibrationEffect.Composed scaledUp = TEST_ONE_SHOT.scale(1.5f);
- assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude());
+ VibrationEffect scaledUp = TEST_ONE_SHOT.scale(1.5f);
+ assertTrue(100 / 255f < getStepSegment(scaledUp, 0).getAmplitude());
- VibrationEffect.Composed scaledDown = TEST_ONE_SHOT.scale(0.5f);
- assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+ VibrationEffect scaledDown = TEST_ONE_SHOT.scale(0.5f);
+ assertTrue(100 / 255f > getStepSegment(scaledDown, 0).getAmplitude());
}
@Test
public void testScaleWaveform() {
- VibrationEffect.Composed scaledUp = TEST_WAVEFORM.scale(1.5f);
- assertEquals(1f, ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude(), 1e-5f);
+ VibrationEffect scaledUp = TEST_WAVEFORM.scale(1.5f);
+ assertEquals(1f, getStepSegment(scaledUp, 0).getAmplitude(), 1e-5f);
- VibrationEffect.Composed scaledDown = TEST_WAVEFORM.scale(0.5f);
- assertTrue(1f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+ VibrationEffect scaledDown = TEST_WAVEFORM.scale(0.5f);
+ assertTrue(1f > getStepSegment(scaledDown, 0).getAmplitude());
}
@Test
public void testScalePrebaked() {
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
- VibrationEffect.Composed scaledUp = effect.scale(1.5f);
+ VibrationEffect scaledUp = effect.scale(1.5f);
assertEquals(effect, scaledUp);
- VibrationEffect.Composed scaledDown = effect.scale(0.5f);
+ VibrationEffect scaledDown = effect.scale(0.5f);
+ assertEquals(effect, scaledDown);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void testScaleVendorEffect() {
+ VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle());
+
+ VibrationEffect scaledUp = effect.scale(1.5f);
+ assertEquals(effect, scaledUp);
+
+ VibrationEffect scaledDown = effect.scale(0.5f);
assertEquals(effect, scaledDown);
}
@Test
public void testScaleComposed() {
- VibrationEffect.Composed effect =
- (VibrationEffect.Composed) VibrationEffect.startComposition()
+ VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
.addEffect(TEST_ONE_SHOT)
.compose();
- VibrationEffect.Composed scaledUp = effect.scale(1.5f);
- assertTrue(0.5f < ((PrimitiveSegment) scaledUp.getSegments().get(0)).getScale());
- assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(1)).getAmplitude());
+ VibrationEffect scaledUp = effect.scale(1.5f);
+ assertTrue(0.5f < getPrimitiveSegment(scaledUp, 0).getScale());
+ assertTrue(100 / 255f < getStepSegment(scaledUp, 1).getAmplitude());
- VibrationEffect.Composed scaledDown = effect.scale(0.5f);
- assertTrue(0.5f > ((PrimitiveSegment) scaledDown.getSegments().get(0)).getScale());
- assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(1)).getAmplitude());
+ VibrationEffect scaledDown = effect.scale(0.5f);
+ assertTrue(0.5f > getPrimitiveSegment(scaledDown, 0).getScale());
+ assertTrue(100 / 255f > getStepSegment(scaledDown, 1).getAmplitude());
+ }
+
+ @Test
+ public void testApplyEffectStrengthToOneShotWaveformAndPrimitives() {
+ VibrationEffect oneShot = VibrationEffect.createOneShot(100, 100);
+ VibrationEffect waveform = VibrationEffect.createWaveform(new long[] { 10, 20 }, 0);
+ VibrationEffect composition = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose();
+
+ assertEquals(oneShot, oneShot.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+ assertEquals(waveform,
+ waveform.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+ assertEquals(composition,
+ composition.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+ }
+
+ @Test
+ public void testApplyEffectStrengthToPredefinedEffect() {
+ VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+
+ VibrationEffect scaledUp =
+ effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG);
+ assertNotEquals(effect, scaledUp);
+ assertEquals(VibrationEffect.EFFECT_STRENGTH_STRONG,
+ getPrebakedSegment(scaledUp, 0).getEffectStrength());
+
+ VibrationEffect scaledDown =
+ effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ assertNotEquals(effect, scaledDown);
+ assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT,
+ getPrebakedSegment(scaledDown, 0).getEffectStrength());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void testApplyEffectStrengthToVendorEffect() {
+ VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle());
+
+ VibrationEffect scaledUp =
+ effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG);
+ assertNotEquals(effect, scaledUp);
+
+ VibrationEffect scaledDown =
+ effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ assertNotEquals(effect, scaledDown);
}
private void doTestApplyRepeatingWithNonRepeatingOriginal(@NotNull VibrationEffect original) {
@@ -819,6 +905,15 @@
}
@Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void testApplyRepeatingIndefinitely_vendorEffect() {
+ VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle());
+
+ assertEquals(effect, effect.applyRepeatingIndefinitely(true, 10));
+ assertEquals(effect, effect.applyRepeatingIndefinitely(false, 10));
+ }
+
+ @Test
public void testDuration() {
assertEquals(1, VibrationEffect.createOneShot(1, 1).getDuration());
assertEquals(-1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK).getDuration());
@@ -832,6 +927,10 @@
new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1).getDuration());
assertEquals(Long.MAX_VALUE, VibrationEffect.createWaveform(
new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0).getDuration());
+ if (Flags.vendorVibrationEffects()) {
+ assertEquals(-1,
+ VibrationEffect.createVendorEffect(createNonEmptyBundle()).getDuration());
+ }
}
@Test
@@ -872,6 +971,19 @@
}
@Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void testAreVibrationFeaturesSupported_vendorEffects() {
+ VibratorInfo supportedVibratorInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS)
+ .build();
+
+ assertTrue(VibrationEffect.createVendorEffect(createNonEmptyBundle())
+ .areVibrationFeaturesSupported(supportedVibratorInfo));
+ assertFalse(VibrationEffect.createVendorEffect(createNonEmptyBundle())
+ .areVibrationFeaturesSupported(new VibratorInfo.Builder(/* id= */ 1).build()));
+ }
+
+ @Test
public void testIsHapticFeedbackCandidate_repeatingEffects_notCandidates() {
assertFalse(VibrationEffect.createWaveform(
new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0).isHapticFeedbackCandidate());
@@ -952,6 +1064,13 @@
assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_TICK).isHapticFeedbackCandidate());
}
+ @Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void testIsHapticFeedbackCandidate_vendorEffects_notCandidates() {
+ assertFalse(VibrationEffect.createVendorEffect(createNonEmptyBundle())
+ .isHapticFeedbackCandidate());
+ }
+
private void assertArrayEq(long[] expected, long[] actual) {
assertTrue(
String.format("Expected pattern %s, but was %s",
@@ -992,4 +1111,35 @@
return context;
}
+
+ private StepSegment getStepSegment(VibrationEffect effect, int index) {
+ VibrationEffectSegment segment = getEffectSegment(effect, index);
+ assertThat(segment).isInstanceOf(StepSegment.class);
+ return (StepSegment) segment;
+ }
+
+ private PrimitiveSegment getPrimitiveSegment(VibrationEffect effect, int index) {
+ VibrationEffectSegment segment = getEffectSegment(effect, index);
+ assertThat(segment).isInstanceOf(PrimitiveSegment.class);
+ return (PrimitiveSegment) segment;
+ }
+
+ private PrebakedSegment getPrebakedSegment(VibrationEffect effect, int index) {
+ VibrationEffectSegment segment = getEffectSegment(effect, index);
+ assertThat(segment).isInstanceOf(PrebakedSegment.class);
+ return (PrebakedSegment) segment;
+ }
+
+ private VibrationEffectSegment getEffectSegment(VibrationEffect effect, int index) {
+ assertThat(effect).isInstanceOf(VibrationEffect.Composed.class);
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ assertThat(index).isLessThan(composed.getSegments().size());
+ return composed.getSegments().get(index);
+ }
+
+ private PersistableBundle createNonEmptyBundle() {
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putInt("key", 1);
+ return bundle;
+ }
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index d3fc49b..b1c9a77 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -35,7 +35,14 @@
) {
// All desktop mode related flags will be added here
DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true),
- WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity, true);
+ WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity, true),
+ MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true),
+ THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true),
+ QUICK_SWITCH(Flags::enableDesktopWindowingQuickSwitch, true),
+ APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true),
+ TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
+ SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true),
+ DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true);
/**
* Determines state of flag based on the actual flag and desktop mode developer option overrides.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index cb087a9..f32683d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -46,6 +46,7 @@
import com.android.wm.shell.compatui.api.CompatUIEvent;
import com.android.wm.shell.compatui.impl.CompatUIEvents.CameraControlStateUpdated;
import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonAppeared;
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import java.util.function.Consumer;
@@ -94,7 +95,7 @@
mCallback = callback;
mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
- && Flags.enableWindowingDynamicInitialBounds()) {
+ && DesktopModeFlags.THEMED_APP_HEADERS.isEnabled(context)) {
// Don't show the SCM button for freeform tasks
mHasSizeCompat &= !taskInfo.isFreeform();
}
@@ -154,7 +155,7 @@
final int prevCameraCompatControlState = mCameraCompatControlState;
mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
- && Flags.enableWindowingDynamicInitialBounds()) {
+ && DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)) {
// Don't show the SCM button for freeform tasks
mHasSizeCompat &= !taskInfo.isFreeform();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 4b548cb..2dc6382 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -946,10 +946,11 @@
@WMSingleton
@Provides
static TaskStackTransitionObserver provideTaskStackTransitionObserver(
+ Context context,
Lazy<Transitions> transitions,
ShellInit shellInit
) {
- return new TaskStackTransitionObserver(transitions, shellInit);
+ return new TaskStackTransitionObserver(context, transitions, shellInit);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 886609a..2ef045d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -80,6 +80,7 @@
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
@@ -348,7 +349,7 @@
wct: WindowContainerTransaction = WindowContainerTransaction(),
transitionSource: DesktopModeTransitionSource,
) {
- if (Flags.enableDesktopWindowingModalsPolicy()
+ if (DesktopModeFlags.MODALS_POLICY.isEnabled(context)
&& isTopActivityExemptFromDesktopWindowing(context, task)) {
ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
@@ -663,7 +664,7 @@
if (taskBoundsBeforeMaximize != null) {
destinationBounds.set(taskBoundsBeforeMaximize)
} else {
- if (Flags.enableWindowingDynamicInitialBounds()) {
+ if (DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) {
destinationBounds.set(calculateInitialBounds(displayLayout, taskInfo))
} else {
destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
@@ -971,7 +972,7 @@
}
private fun isIncompatibleTask(task: TaskInfo) =
- Flags.enableDesktopWindowingModalsPolicy()
+ DesktopModeFlags.MODALS_POLICY.isEnabled(context)
&& isTopActivityExemptFromDesktopWindowing(context, task)
private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean {
@@ -1361,7 +1362,7 @@
// Start a new jank interaction for the drag release to desktop window animation.
interactionJankMonitor.begin(taskSurface, context,
CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop")
- if (Flags.enableWindowingDynamicInitialBounds()) {
+ if (DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) {
finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo))
} else {
finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index d001b2c..54f908b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -20,7 +20,6 @@
import static android.content.pm.PackageManager.FEATURE_PC;
import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps;
-import static com.android.window.flags.Flags.enableTaskStackObserverInShell;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
import android.app.ActivityManager;
@@ -55,6 +54,7 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -351,7 +351,7 @@
private void notifyTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
if (mListener == null
- || !enableTaskStackObserverInShell()
+ || !DesktopModeFlags.TASK_STACK_OBSERVER_IN_SHELL.isEnabled(mContext)
|| taskInfo.realActivity == null) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
index 8ee72b4..3a0bdb9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -18,13 +18,14 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.Context
import android.os.IBinder
import android.util.ArrayMap
import android.view.SurfaceControl
import android.view.WindowManager
import android.window.TransitionInfo
-import com.android.window.flags.Flags.enableTaskStackObserverInShell
import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import dagger.Lazy
@@ -37,6 +38,7 @@
* TODO(346588978) Move split/pip signals here as well so that launcher don't need to handle it
*/
class TaskStackTransitionObserver(
+ private val context: Context,
private val transitions: Lazy<Transitions>,
shellInit: ShellInit
) : Transitions.TransitionObserver {
@@ -62,7 +64,7 @@
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction
) {
- if (enableTaskStackObserverInShell()) {
+ if (DesktopModeFlags.TASK_STACK_OBSERVER_IN_SHELL.isEnabled(context)) {
val taskInfoList = mutableListOf<RunningTaskInfo>()
val transitionTypeList = mutableListOf<Int>()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index a77a76c..7f1f365 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -101,6 +101,7 @@
import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
@@ -1124,7 +1125,7 @@
&& taskInfo.isFocused) {
return false;
}
- if (Flags.enableDesktopWindowingModalsPolicy()
+ if (DesktopModeFlags.MODALS_POLICY.isEnabled(mContext)
&& isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) {
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index a1cc650..9a7ce67 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -75,6 +75,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
@@ -630,7 +631,7 @@
// TODO(b/301119301): consider moving the config data needed for diffs to relayout params
// instead of using a whole Configuration as a parameter.
final Configuration windowDecorConfig = new Configuration();
- if (Flags.enableAppHeaderWithTaskDensity() && isAppHeader) {
+ if (DesktopModeFlags.APP_HEADER_WITH_TASK_DENSITY.isEnabled(context) && isAppHeader) {
// Should match the density of the task. The task may have had its density overridden
// to be different that SysUI's.
windowDecorConfig.setTo(taskInfo.configuration);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 2fd3eaa..0f2de70 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -30,9 +30,9 @@
import androidx.annotation.NonNull;
-import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
/**
@@ -245,7 +245,7 @@
private static boolean isSizeConstraintForDesktopModeEnabled(Context context) {
return DesktopModeStatus.canEnterDesktopMode(context)
- && Flags.enableDesktopWindowingSizeConstraints();
+ && DesktopModeFlags.SIZE_CONSTRAINTS.isEnabled(context);
}
interface DragStartListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
index 4f04901..4faed01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
@@ -31,8 +31,8 @@
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import androidx.core.content.ContextCompat
-import com.android.window.flags.Flags
import com.android.wm.shell.R
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags
private const val OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS = 350
private const val MAX_DRAWABLE_ALPHA = 255
@@ -108,7 +108,7 @@
baseForegroundColor: Int? = null,
rippleDrawable: RippleDrawable? = null
) {
- if (Flags.enableThemedAppHeaders()) {
+ if (DesktopModeFlags.THEMED_APP_HEADERS.isEnabled(context)) {
requireNotNull(iconForegroundColor) { "Icon foreground color must be non-null" }
requireNotNull(baseForegroundColor) { "Base foreground color must be non-null" }
requireNotNull(rippleDrawable) { "Ripple drawable must be non-null" }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index b704d9c..17b3dea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -42,8 +42,8 @@
import com.android.internal.R.attr.materialColorSurfaceContainerHigh
import com.android.internal.R.attr.materialColorSurfaceContainerLow
import com.android.internal.R.attr.materialColorSurfaceDim
-import com.android.window.flags.Flags
import com.android.wm.shell.R
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags
import com.android.wm.shell.windowdecor.MaximizeButtonView
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.common.OPACITY_100
@@ -144,7 +144,7 @@
height: Int,
isCaptionVisible: Boolean
) {
- if (Flags.enableThemedAppHeaders()) {
+ if (DesktopModeFlags.THEMED_APP_HEADERS.isEnabled(context)) {
bindDataWithThemedHeaders(taskInfo)
} else {
bindDataLegacy(taskInfo)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
index 0e5efa6..bc9b44e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
@@ -18,6 +18,7 @@
import android.app.ActivityManager
import android.app.WindowConfiguration
+import android.content.Context
import android.os.IBinder
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
@@ -59,6 +60,7 @@
@JvmField @Rule val setFlagsRule = SetFlagsRule()
+ @Mock private lateinit var context: Context
@Mock private lateinit var shellInit: ShellInit
@Mock lateinit var testExecutor: ShellExecutor
@Mock private lateinit var transitionsLazy: Lazy<Transitions>
@@ -72,7 +74,7 @@
MockitoAnnotations.initMocks(this)
shellInit = Mockito.spy(ShellInit(testExecutor))
whenever(transitionsLazy.get()).thenReturn(transitions)
- transitionObserver = TaskStackTransitionObserver(transitionsLazy, shellInit)
+ transitionObserver = TaskStackTransitionObserver(context, transitionsLazy, shellInit)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java)
verify(shellInit)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 5b328b8..fb13b57 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -712,9 +712,7 @@
val hasReachedTargetScene =
(targetScene == toScene && progress >= 1f) ||
(targetScene == fromScene && progress <= 0f)
- val skipAnimation =
- hasReachedTargetScene &&
- currentOverscrollSpec?.transformationSpec?.transformations?.isEmpty() == true
+ val skipAnimation = hasReachedTargetScene && !canOverscroll()
return startOffsetAnimation {
val animatable = Animatable(dragOffset, OffsetVisibilityThreshold)
@@ -767,12 +765,7 @@
// Immediately stop this transition if we are bouncing on a
// scene that does not bounce.
- val overscrollSpec = currentOverscrollSpec
- if (
- overscrollSpec != null &&
- overscrollSpec.transformationSpec.transformations
- .isEmpty()
- ) {
+ if (!canOverscroll()) {
snapToScene(targetScene)
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 2b78b5a..324e7bd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -115,18 +115,6 @@
}
}
-private val TRAVERSE_KEY = Any()
-
-/** Find the nearest [PointersInfoOwner] ancestor or throw. */
-internal fun DelegatableNode.requireAncestorPointersInfoOwner(): PointersInfoOwner {
- val ancestorNode =
- checkNotNull(findNearestAncestor(TRAVERSE_KEY)) {
- "This should never happen! Couldn't find a MultiPointerDraggableNode. " +
- "Are we inside an SceneTransitionLayout?"
- }
- return ancestorNode as PointersInfoOwner
-}
-
internal class MultiPointerDraggableNode(
orientation: Orientation,
enabled: () -> Boolean,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 08e8e72..2a739d7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -233,6 +233,12 @@
}
}
+ /** Returns if the [progress] value of this transition can go beyond range `[0; 1]` */
+ fun canOverscroll(): Boolean {
+ val overscrollSpec = currentOverscrollSpec ?: return true
+ return overscrollSpec.transformationSpec.transformations.isNotEmpty()
+ }
+
/**
* An animatable that animates from 1f to 0f. This will be used to nicely animate the sudden
* jump of values when this transitions interrupts another one.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index bbd2f6b..9ccf99b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dock.dockManager
import com.android.systemui.dock.fakeDockManager
@@ -106,6 +107,27 @@
@Test
@DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ fun keyguardGoesAway_whenLaunchingEditMode_doNotForceBlankScene() =
+ with(kosmos) {
+ testScope.runTest {
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ communalSceneInteractor.setEditModeState(EditModeState.STARTING)
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ testScope = this
+ )
+
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
+ @Test
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun keyguardGoesAway_whenLaunchingWidget_doNotForceBlankScene() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt
new file mode 100644
index 0000000..5b629b9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.communal.widgets
+
+import android.content.ComponentName
+import android.content.Intent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_PRESELECTED_KEY
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class EditWidgetsActivityStarterTest : SysuiTestCase() {
+ private val activityStarter = mock<ActivityStarter>()
+ private val kosmos = testKosmos()
+
+ private lateinit var component: ComponentName
+ private lateinit var underTest: EditWidgetsActivityStarter
+
+ @Before
+ fun setUp() {
+ component = ComponentName(context, EditWidgetsActivity::class.java)
+ underTest =
+ EditWidgetsActivityStarterImpl(
+ context.applicationContext,
+ activityStarter,
+ )
+ }
+
+ @Test
+ fun activityLaunch_intentIsWellFormed() {
+ with(kosmos) {
+ testScope.runTest {
+ underTest.startActivity(TEST_PRESELECTED_KEY, shouldOpenWidgetPickerOnStart = true)
+
+ val captor = argumentCaptor<Intent>()
+ verify(activityStarter)
+ .startActivityDismissingKeyguard(captor.capture(), eq(true), eq(true), any())
+ assertThat(captor.lastValue.component).isEqualTo(component)
+ assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_NEW_TASK).isNotEqualTo(0)
+ assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .isNotEqualTo(0)
+ assertThat(captor.lastValue.extras?.getString(EXTRA_PRESELECTED_KEY))
+ .isEqualTo(TEST_PRESELECTED_KEY)
+ assertThat(
+ captor.lastValue.extras?.getBoolean(
+ EditWidgetsActivity.EXTRA_OPEN_WIDGET_PICKER_ON_START
+ )
+ )
+ .isEqualTo(true)
+
+ underTest.startActivity(TEST_PRESELECTED_KEY, shouldOpenWidgetPickerOnStart = false)
+
+ verify(activityStarter, times(2))
+ .startActivityDismissingKeyguard(captor.capture(), eq(true), eq(true), any())
+ assertThat(captor.lastValue.component).isEqualTo(component)
+ assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_NEW_TASK).isNotEqualTo(0)
+ assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .isNotEqualTo(0)
+ assertThat(captor.lastValue.extras?.getString(EXTRA_PRESELECTED_KEY))
+ .isEqualTo(TEST_PRESELECTED_KEY)
+ assertThat(
+ captor.lastValue.extras?.getBoolean(
+ EditWidgetsActivity.EXTRA_OPEN_WIDGET_PICKER_ON_START
+ )
+ )
+ .isEqualTo(false)
+ }
+ }
+ }
+
+ companion object {
+ const val TEST_PRESELECTED_KEY = "test-key"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 3fd1c20..d9708a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -362,6 +362,7 @@
}
@Test
+ @DisableSceneContainer
fun dreamingLockscreenHostedToLockscreen() =
testScope.runTest {
// GIVEN a device dreaming with the lockscreen hosted dream and not dozing
@@ -449,6 +450,7 @@
}
@Test
+ @DisableSceneContainer
fun dreamingLockscreenHostedToDozing() =
testScope.runTest {
// GIVEN a device is dreaming with lockscreen hosted dream
@@ -480,6 +482,7 @@
}
@Test
+ @DisableSceneContainer
fun dreamingLockscreenHostedToOccluded() =
testScope.runTest {
// GIVEN device is dreaming with lockscreen hosted dream and not occluded
@@ -977,6 +980,7 @@
}
@Test
+ @DisableSceneContainer
fun alternateBouncerToGlanceableHub() =
testScope.runTest {
// GIVEN the device is idle on the glanceable hub
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 23b28e3..1df2553 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -27,6 +27,8 @@
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -64,6 +66,7 @@
private val placeholderViewModel by lazy { kosmos.notificationsPlaceholderViewModel }
private val scrollViewModel by lazy { kosmos.notificationScrollViewModel }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val fakeKeyguardRepository by lazy { kosmos.fakeKeyguardRepository }
private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }
@Test
@@ -73,9 +76,11 @@
val leftOffset = MutableStateFlow(0)
val shape by collectLastValue(scrollViewModel.shadeScrimShape(radius, leftOffset))
+ // When: receive scrim bounds
placeholderViewModel.onScrimBoundsChanged(
ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f)
)
+ // Then: shape is updated
assertThat(shape)
.isEqualTo(
ShadeScrimShape(
@@ -86,11 +91,13 @@
)
)
+ // When: receive new scrim bounds
leftOffset.value = 200
radius.value = 24
placeholderViewModel.onScrimBoundsChanged(
ShadeScrimBounds(left = 210f, top = 200f, right = 300f, bottom = 550f)
)
+ // Then: shape is updated
assertThat(shape)
.isEqualTo(
ShadeScrimShape(
@@ -100,6 +107,16 @@
bottomRadius = 0
)
)
+
+ // When: QuickSettings shows up full screen
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(Scenes.QuickSettings)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ // Then: shape is null
+ assertThat(shape).isNull()
}
@Test
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index 6b7712d..b7c02ea 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -26,6 +26,7 @@
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -103,11 +104,14 @@
.mapLatest(::determineSceneAfterTransition)
.filterNotNull()
.onEach { (nextScene, nextTransition) ->
- if (!communalSceneInteractor.isLaunchingWidget.value) {
- // When launching a widget, we don't want to animate the scene change or the
- // Communal Hub will reveal the wallpaper even though it shouldn't. Instead
- // we snap to the new scene as part of the launch animation, once the
- // activity launch is done, so we don't change scene here.
+ // When launching a widget, we don't want to animate the scene change or the
+ // Communal Hub will reveal the wallpaper even though it shouldn't. Instead we
+ // snap to the new scene as part of the launch animation, once the activity
+ // launch is done, so we don't change scene here.
+ val delaySceneTransition =
+ communalSceneInteractor.editModeState.value == EditModeState.STARTING ||
+ communalSceneInteractor.isLaunchingWidget.value
+ if (!delaySceneTransition) {
communalSceneInteractor.changeScene(nextScene, nextTransition)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index b44a8cf..a915241 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -26,7 +26,6 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
@@ -91,12 +90,12 @@
edgeWithoutSceneContainer =
Edge.create(from = KeyguardState.ALTERNATE_BOUNCER, to = KeyguardState.GONE)
)
- .map<TransitionStep, Boolean?> {
+ .map {
// The alt bouncer is pretty fast to hide, so start the surface behind animation
// around 30%.
it.value > 0.3f
}
- .onStart {
+ .onStart<Boolean?> {
// Default to null ("don't care, use a reasonable default").
emit(null)
}
@@ -145,6 +144,7 @@
}
} else {
if (isIdleOnCommunal) {
+ if (SceneContainerFlag.isEnabled) return@collect
KeyguardState.GLANCEABLE_HUB
} else if (isOccluded) {
KeyguardState.OCCLUDED
@@ -158,7 +158,6 @@
}
private fun listenForAlternateBouncerToGone() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
if (KeyguardWmStateRefactor.isEnabled) {
// Handled via #dismissAlternateBouncer.
@@ -170,9 +169,9 @@
keyguardInteractor.isKeyguardGoingAway.filter { it }.map {}, // map to Unit
keyguardInteractor.isKeyguardOccluded.flatMapLatest { keyguardOccluded ->
if (keyguardOccluded) {
- primaryBouncerInteractor.keyguardAuthenticatedBiometricsHandled.drop(
- 1
- ) // drop the initial state
+ primaryBouncerInteractor.keyguardAuthenticatedBiometricsHandled
+ // drop the initial state
+ .drop(1)
} else {
emptyFlow()
}
@@ -184,7 +183,6 @@
}
private fun listenForAlternateBouncerToPrimaryBouncer() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
scope.launch {
keyguardInteractor.primaryBouncerShowing
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index 117dbcf..f3bd0e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -61,6 +61,7 @@
) {
override fun start() {
+ if (SceneContainerFlag.isEnabled) return
listenForDreamingLockscreenHostedToLockscreen()
listenForDreamingLockscreenHostedToGone()
listenForDreamingLockscreenHostedToDozing()
@@ -96,8 +97,6 @@
}
private fun listenForDreamingLockscreenHostedToPrimaryBouncer() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
- if (SceneContainerFlag.isEnabled) return
scope.launch {
keyguardInteractor.primaryBouncerShowing
.filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing }
@@ -106,8 +105,6 @@
}
private fun listenForDreamingLockscreenHostedToGone() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
- if (SceneContainerFlag.isEnabled) return
scope.launch {
keyguardInteractor.biometricUnlockState
.filterRelevantKeyguardStateAnd { biometricUnlockState ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 4c3a75e..3775d19 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -100,7 +100,6 @@
private fun listenForDreamingToGlanceableHub() {
if (!communalSettingsInteractor.isCommunalFlagEnabled()) return
- // TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
scope.launch("$TAG#listenForDreamingToGlanceableHub", mainDispatcher) {
glanceableHubTransitions.listenForGlanceableHubTransition(
@@ -195,7 +194,7 @@
private fun listenForDreamingToGoneWhenDismissable() {
if (SceneContainerFlag.isEnabled) {
- return // TODO(b/336576536): Check if adaptation for scene framework is needed
+ return
}
if (KeyguardWmStateRefactor.isEnabled) {
@@ -217,7 +216,7 @@
}
private fun listenForDreamingToGoneFromBiometricUnlock() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
+ // TODO(b/353542570): Adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
scope.launch {
keyguardInteractor.biometricUnlockState
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 6b1be93c..91ee287 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -75,7 +75,6 @@
) {
override fun start() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index ef76f38..8f4110c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -69,7 +69,7 @@
) {
override fun start() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
+ // KeyguardState.GONE does not exist with SceneContainerFlag enabled
if (SceneContainerFlag.isEnabled) return
listenForGoneToAodOrDozing()
listenForGoneToDreaming()
@@ -100,21 +100,19 @@
}
}
- if (!SceneContainerFlag.isEnabled) {
- scope.launch {
- keyguardRepository.isKeyguardEnabled
- .filterRelevantKeyguardStateAnd { enabled -> enabled }
- .sample(keyguardEnabledInteractor.showKeyguardWhenReenabled)
- .filter { reshow -> reshow }
- .collect {
- startTransitionTo(
- KeyguardState.LOCKSCREEN,
- ownerReason =
- "Keyguard was re-enabled, and we weren't GONE when it " +
- "was originally disabled"
- )
- }
- }
+ scope.launch {
+ keyguardRepository.isKeyguardEnabled
+ .filterRelevantKeyguardStateAnd { enabled -> enabled }
+ .sample(keyguardEnabledInteractor.showKeyguardWhenReenabled)
+ .filter { reshow -> reshow }
+ .collect {
+ startTransitionTo(
+ KeyguardState.LOCKSCREEN,
+ ownerReason =
+ "Keyguard was re-enabled, and we weren't GONE when it " +
+ "was originally disabled"
+ )
+ }
}
} else {
scope.launch("$TAG#listenForGoneToLockscreenOrHub") {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 16c014f..206bbc5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -351,7 +351,6 @@
* keyguard transition.
*/
private fun listenForLockscreenToGlanceableHub() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 2f32040..710b710a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -143,7 +143,6 @@
if (restartDreamOnUnocclude() && dreamFromOccluded) {
startTransitionTo(KeyguardState.DREAMING)
} else if (isIdleOnCommunal || showCommunalFromOccluded) {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
if (communalSceneKtfRefactor()) {
communalSceneInteractor.changeScene(
@@ -159,8 +158,6 @@
}
private fun listenForOccludedToGone() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
- if (SceneContainerFlag.isEnabled) return
if (KeyguardWmStateRefactor.isEnabled) {
// We don't think OCCLUDED to GONE is possible. You should always have to go via a
// *_BOUNCER state to end up GONE. Launching an activity over a dismissable keyguard
@@ -168,7 +165,8 @@
// If we're wrong - sorry, add it back here.
return
}
-
+ // TODO(b/353545202): Adaptation for scene framework is needed
+ if (SceneContainerFlag.isEnabled) return
scope.launch {
keyguardInteractor.isKeyguardOccluded
.sample(keyguardInteractor.isKeyguardShowing, ::Pair)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 9adcaa2..e2d7851 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -103,7 +103,6 @@
}
private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
if (KeyguardWmStateRefactor.isEnabled) {
scope.launch {
@@ -177,13 +176,11 @@
}
private fun listenForPrimaryBouncerToAsleep() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
scope.launch { listenForSleepTransition() }
}
private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
scope.launch {
keyguardInteractor.primaryBouncerShowing
@@ -197,7 +194,6 @@
}
private fun listenForPrimaryBouncerToGone() {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
if (KeyguardWmStateRefactor.isEnabled) {
// This is handled in KeyguardSecurityContainerController and
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
index af1ce2b..f9ab1bb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -50,7 +50,6 @@
fromState: KeyguardState,
toState: KeyguardState,
) {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
val toScene =
if (fromState == KeyguardState.GLANCEABLE_HUB) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index 1152d70..1c445a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -27,6 +27,8 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.domain.resolver.NotifShadeSceneFamilyResolver
+import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolver
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
@@ -57,6 +59,8 @@
@Application private val applicationScope: CoroutineScope,
sceneInteractor: SceneInteractor,
deviceEntryInteractor: DeviceEntryInteractor,
+ quickSettingsSceneFamilyResolver: QuickSettingsSceneFamilyResolver,
+ notifShadeSceneFamilyResolver: NotifShadeSceneFamilyResolver,
) {
val dismissAction: Flow<DismissAction> = repository.dismissAction
@@ -96,10 +100,8 @@
deviceEntryInteractor.isUnlocked,
) { scene, isUnlocked ->
isUnlocked &&
- (scene == Scenes.Shade ||
- scene == Scenes.NotificationsShade ||
- scene == Scenes.QuickSettings ||
- scene == Scenes.QuickSettingsShade)
+ (quickSettingsSceneFamilyResolver.includesScene(scene) ||
+ notifShadeSceneFamilyResolver.includesScene(scene))
}
.distinctUntilChanged()
val executeDismissAction: Flow<() -> KeyguardDone> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index f9bfaff..797d466 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -398,7 +398,6 @@
* including KeyguardSecurityContainerController and WindowManager.
*/
fun startDismissKeyguardTransition(reason: String = "") {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)")
when (val startedState = repository.currentTransitionInfoInternal.value.to) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index c1caeed..91bfae3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -16,7 +16,6 @@
package com.android.systemui.shade;
-import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
@@ -57,7 +56,6 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.StatusBarManager;
import android.content.ContentResolver;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -233,7 +231,6 @@
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
-import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.util.Compile;
import com.android.systemui.util.Utils;
@@ -705,7 +702,6 @@
FalsingCollector falsingCollector,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
- StatusBarWindowStateController statusBarWindowStateController,
NotificationShadeWindowController notificationShadeWindowController,
DozeLog dozeLog,
DozeParameters dozeParameters,
@@ -913,7 +909,6 @@
mMediaDataManager = mediaDataManager;
mTapAgainViewController = tapAgainViewController;
mSysUiState = sysUiState;
- statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
mKeyguardBypassController = bypassController;
mUpdateMonitor = keyguardUpdateMonitor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
@@ -4882,16 +4877,6 @@
return mStatusBarStateListener;
}
- private void onStatusBarWindowStateChanged(@StatusBarManager.WindowVisibleState int state) {
- if (state != WINDOW_STATE_SHOWING
- && mStatusBarStateController.getState() == StatusBarState.SHADE) {
- collapse(
- false /* animate */,
- false /* delayed */,
- 1.0f /* speedUpFactor */);
- }
- }
-
/** Handles MotionEvents for the Shade. */
public final class TouchHandler implements View.OnTouchListener, Gefingerpoken {
private long mLastTouchDownTime = -1L;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index f39ee9a..9e221d3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.domain.interactor
+import com.android.app.tracing.FlowTracing.traceAsCounter
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -74,6 +75,7 @@
}
}
.distinctUntilChanged()
+ .traceAsCounter("panel_expansion") { (it * 100f).toInt() }
.stateIn(scope, SharingStarted.Eagerly, 0f)
override val qsExpansion: StateFlow<Float> = repository.qsExpansion
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index b2142a5..9617b54 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.domain.interactor
+import com.android.app.tracing.FlowTracing.traceAsCounter
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.dagger.SysUISingleton
@@ -46,6 +47,7 @@
) : BaseShadeInteractor {
override val shadeExpansion: StateFlow<Float> =
sceneBasedExpansion(sceneInteractor, SceneFamilies.NotifShade)
+ .traceAsCounter("panel_expansion") { (it * 100f).toInt() }
.stateIn(scope, SharingStarted.Eagerly, 0f)
private val sceneBasedQsExpansion =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 57e52b7..2ba79a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -112,14 +112,22 @@
private operator fun SceneKey.contains(scene: SceneKey) =
sceneInteractor.isSceneInFamily(scene, this)
+ private val qsAllowsClipping: Flow<Boolean> =
+ combine(shadeInteractor.shadeMode, shadeInteractor.qsExpansion) { shadeMode, qsExpansion ->
+ qsExpansion < 0.5f || shadeMode != ShadeMode.Single
+ }
+ .distinctUntilChanged()
+
/** The bounds of the notification stack in the current scene. */
private val shadeScrimClipping: Flow<ShadeScrimClipping?> =
combine(
+ qsAllowsClipping,
stackAppearanceInteractor.shadeScrimBounds,
stackAppearanceInteractor.shadeScrimRounding,
- ) { bounds, rounding ->
- bounds?.let { ShadeScrimClipping(it, rounding) }
+ ) { qsAllowsClipping, bounds, rounding ->
+ bounds?.takeIf { qsAllowsClipping }?.let { ShadeScrimClipping(it, rounding) }
}
+ .distinctUntilChanged()
.dumpWhileCollecting("stackClipping")
fun shadeScrimShape(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
index 91bfdff..3b392c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -50,14 +50,14 @@
Surface(
color = tileColor,
shape = RoundedCornerShape(16.dp),
- modifier =
- Modifier.combinedClickable(
- onClick = viewModel.onClick,
- onLongClick = viewModel.onLongClick
- ),
) {
Row(
- modifier = Modifier.padding(20.dp),
+ modifier =
+ Modifier.combinedClickable(
+ onClick = viewModel.onClick,
+ onLongClick = viewModel.onLongClick
+ )
+ .padding(20.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement =
Arrangement.spacedBy(
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
index f3b9cc1..9ae6674 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -40,14 +40,11 @@
public class ToastFactory implements Dumpable {
// only one ToastPlugin can be connected at a time.
private ToastPlugin mPlugin;
- private final LayoutInflater mLayoutInflater;
@Inject
public ToastFactory(
- LayoutInflater layoutInflater,
PluginManager pluginManager,
DumpManager dumpManager) {
- mLayoutInflater = layoutInflater;
dumpManager.registerDumpable("ToastFactory", this);
pluginManager.addPluginListener(
new PluginListener<ToastPlugin>() {
@@ -70,11 +67,12 @@
*/
public SystemUIToast createToast(Context context, CharSequence text, String packageName,
int userId, int orientation) {
+ LayoutInflater layoutInflater = LayoutInflater.from(context);
if (isPluginAvailable()) {
- return new SystemUIToast(mLayoutInflater, context, text, mPlugin.createToast(text,
+ return new SystemUIToast(layoutInflater, context, text, mPlugin.createToast(text,
packageName, userId), packageName, userId, orientation);
}
- return new SystemUIToast(mLayoutInflater, context, text, packageName, userId,
+ return new SystemUIToast(layoutInflater, context, text, packageName, userId,
orientation);
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 85a455d..bbfa32b 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -128,7 +128,7 @@
return;
}
Context displayContext = context.createDisplayContext(display);
- mToast = mToastFactory.createToast(mContext /* sysuiContext */, text, packageName,
+ mToast = mToastFactory.createToast(displayContext /* sysuiContext */, text, packageName,
userHandle.getIdentifier(), mOrientation);
if (mToast.getInAnimation() != null) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index a310520..32d059b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -33,6 +33,8 @@
import com.android.systemui.scene.data.repository.Transition
import com.android.systemui.scene.data.repository.setSceneTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.resolver.notifShadeSceneFamilyResolver
+import com.android.systemui.scene.domain.resolver.quickSettingsSceneFamilyResolver
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -78,6 +80,8 @@
applicationScope = testScope.backgroundScope,
sceneInteractor = kosmos.sceneInteractor,
deviceEntryInteractor = kosmos.deviceEntryInteractor,
+ quickSettingsSceneFamilyResolver = kosmos.quickSettingsSceneFamilyResolver,
+ notifShadeSceneFamilyResolver = kosmos.notifShadeSceneFamilyResolver,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index a5c4bcd..56fb43d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -698,7 +698,6 @@
mFalsingManager, new FalsingCollectorFake(),
mKeyguardStateController,
mStatusBarStateController,
- mStatusBarWindowStateController,
mNotificationShadeWindowController,
mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
mLatencyTracker, mAccessibilityManager, 0, mUpdateMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 8df37ce..666bdd6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -134,7 +134,6 @@
mNotificationManager,
mAccessibilityManager,
new ToastFactory(
- mLayoutInflater,
mPluginManager,
mDumpManager),
mToastLogger);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index fe156e2..957f092 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -21,6 +21,8 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.resolver.notifShadeSceneFamilyResolver
+import com.android.systemui.scene.domain.resolver.quickSettingsSceneFamilyResolver
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ExperimentalCoroutinesApi
@@ -33,5 +35,7 @@
applicationScope = testScope.backgroundScope,
sceneInteractor = sceneInteractor,
deviceEntryInteractor = deviceEntryInteractor,
+ quickSettingsSceneFamilyResolver = quickSettingsSceneFamilyResolver,
+ notifShadeSceneFamilyResolver = notifShadeSceneFamilyResolver,
)
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9d4310c..363c1d8 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -148,7 +148,7 @@
"android.hardware.common-V2-java",
"android.hardware.light-V2.0-java",
"android.hardware.gnss-V2-java",
- "android.hardware.vibrator-V2-java",
+ "android.hardware.vibrator-V3-java",
"app-compat-annotations",
"framework-tethering.stubs.module_lib",
"keepanno-annotations",
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index bb2efa1..36a9c80 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1355,8 +1355,7 @@
int patternRepeatIndex = -1;
int amplitudeCount = -1;
- if (effect instanceof VibrationEffect.Composed) {
- VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ if (effect instanceof VibrationEffect.Composed composed) {
int segmentCount = composed.getSegments().size();
pattern = new long[segmentCount];
amplitudes = new int[segmentCount];
@@ -1381,6 +1380,8 @@
}
pattern[amplitudeCount++] = segment.getDuration();
}
+ } else {
+ Slog.w(TAG, "Input devices don't support effect " + effect);
}
if (amplitudeCount < 0) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ed71765..64b6dbe 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -570,13 +570,12 @@
@GuardedBy("ImfLock.class")
private void onSecureSettingsChangedLocked(@NonNull String key, @UserIdInt int userId) {
- if (!mConcurrentMultiUserModeEnabled && userId != mCurrentUserId) {
- return;
- }
switch (key) {
case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: {
if (!Flags.imeSwitcherRevamp()) {
- mMenuController.updateKeyboardFromSettingsLocked();
+ if (userId == mCurrentUserId) {
+ mMenuController.updateKeyboardFromSettingsLocked();
+ }
}
break;
}
@@ -679,12 +678,10 @@
DirectBootAwareness.AUTO);
InputMethodSettingsRepository.put(userId, settings);
- if (mConcurrentMultiUserModeEnabled || userId == mCurrentUserId) {
- postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */, userId);
- // If the locale is changed, needs to reset the default ime
- resetDefaultImeLocked(mContext, userId);
- updateFromSettingsLocked(true, userId);
- }
+ postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */, userId);
+ // If the locale is changed, needs to reset the default ime
+ resetDefaultImeLocked(mContext, userId);
+ updateFromSettingsLocked(true, userId);
}
}
}
@@ -786,14 +783,7 @@
int change = isPackageDisappearing(imi.getPackageName());
if (change == PACKAGE_PERMANENT_CHANGE) {
Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent());
- if (isCurrentUser) {
- setInputMethodEnabledLocked(imi.getId(), false, userId);
- } else {
- settings.buildAndPutEnabledInputMethodsStrRemovingId(
- new StringBuilder(),
- settings.getEnabledInputMethodsAndSubtypeList(),
- imi.getId());
- }
+ setInputMethodEnabledLocked(imi.getId(), false, userId);
} else if (change == PACKAGE_UPDATING) {
Slog.i(TAG, "Input method reinstalling, clearing additional subtypes: "
+ imi.getComponent());
@@ -6696,36 +6686,8 @@
private boolean handleShellCommandEnableDisableInputMethodInternalLocked(
@UserIdInt int userId, String imeId, boolean enabled, PrintWriter out,
PrintWriter error) {
- boolean failedToEnableUnknownIme = false;
- boolean previouslyEnabled = false;
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- if (userId == mCurrentUserId) {
- if (enabled && !settings.getMethodMap().containsKey(imeId)) {
- failedToEnableUnknownIme = true;
- } else {
- previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled, userId);
- }
- } else {
- if (enabled) {
- if (!settings.getMethodMap().containsKey(imeId)) {
- failedToEnableUnknownIme = true;
- } else {
- final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
- final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
- enabledImeIdsStr, imeId);
- previouslyEnabled = TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr);
- if (!previouslyEnabled) {
- settings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
- }
- }
- } else {
- previouslyEnabled =
- settings.buildAndPutEnabledInputMethodsStrRemovingId(
- new StringBuilder(),
- settings.getEnabledInputMethodsAndSubtypeList(), imeId);
- }
- }
- if (failedToEnableUnknownIme) {
+ if (enabled && !settings.getMethodMap().containsKey(imeId)) {
error.print("Unknown input method ");
error.print(imeId);
error.println(" cannot be enabled for user #" + userId);
@@ -6734,6 +6696,8 @@
+ " failed due to its unrecognized IME ID.");
return false;
}
+
+ final boolean previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled, userId);
out.print("Input method ");
out.print(imeId);
out.print(": ");
diff --git a/services/core/java/com/android/server/vibrator/AbstractComposedVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractComposedVibratorStep.java
new file mode 100644
index 0000000..b263159
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/AbstractComposedVibratorStep.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+
+import java.util.List;
+
+/**
+ * Represent a step on a single vibrator that plays one or more segments from a
+ * {@link VibrationEffect.Composed} effect.
+ */
+abstract class AbstractComposedVibratorStep extends AbstractVibratorStep {
+ public final VibrationEffect.Composed effect;
+ public final int segmentIndex;
+
+ /**
+ * @param conductor The {@link VibrationStepConductor} for these steps.
+ * @param startTime The time to schedule this step in the conductor.
+ * @param controller The vibrator that is playing the effect.
+ * @param effect The effect being played in this step.
+ * @param index The index of the next segment to be played by this step
+ * @param pendingVibratorOffDeadline The time the vibrator is expected to complete any
+ * previous vibration and turn off. This is used to allow this step to
+ * be triggered when the completion callback is received, and can
+ * be used to play effects back-to-back.
+ */
+ AbstractComposedVibratorStep(VibrationStepConductor conductor, long startTime,
+ VibratorController controller, VibrationEffect.Composed effect, int index,
+ long pendingVibratorOffDeadline) {
+ super(conductor, startTime, controller, pendingVibratorOffDeadline);
+ this.effect = effect;
+ this.segmentIndex = index;
+ }
+
+ /**
+ * Return the {@link VibrationStepConductor#nextVibrateStep} with start and off timings
+ * calculated from {@link #getVibratorOnDuration()} based on the current
+ * {@link SystemClock#uptimeMillis()} and jumping all played segments from the effect.
+ */
+ protected List<Step> nextSteps(int segmentsPlayed) {
+ // Schedule next steps to run right away.
+ long nextStartTime = SystemClock.uptimeMillis();
+ if (mVibratorOnResult > 0) {
+ // Vibrator was turned on by this step, with mVibratorOnResult as the duration.
+ // Schedule next steps for right after the vibration finishes.
+ nextStartTime += mVibratorOnResult;
+ }
+ return nextSteps(nextStartTime, segmentsPlayed);
+ }
+
+ /**
+ * Return the {@link VibrationStepConductor#nextVibrateStep} with given start time,
+ * which might be calculated independently, and jumping all played segments from the effect.
+ *
+ * <p>This should be used when the vibrator on/off state is not responsible for the step
+ * execution timing, e.g. while playing the vibrator amplitudes.
+ */
+ protected List<Step> nextSteps(long nextStartTime, int segmentsPlayed) {
+ int nextSegmentIndex = segmentIndex + segmentsPlayed;
+ int effectSize = effect.getSegments().size();
+ int repeatIndex = effect.getRepeatIndex();
+ if (nextSegmentIndex >= effectSize && repeatIndex >= 0) {
+ // Count the loops that were played.
+ int loopSize = effectSize - repeatIndex;
+ int loopSegmentsPlayed = nextSegmentIndex - repeatIndex;
+ getVibration().stats.reportRepetition(loopSegmentsPlayed / loopSize);
+ nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize);
+ }
+ Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
+ nextSegmentIndex, mPendingVibratorOffDeadline);
+ return List.of(nextStep);
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index 90b6f95..42203b1 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -16,21 +16,16 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.os.SystemClock;
-import android.os.VibrationEffect;
import android.util.Slog;
import java.util.Arrays;
import java.util.List;
-/**
- * Represent a step on a single vibrator that plays one or more segments from a
- * {@link VibrationEffect.Composed} effect.
- */
+/** Represent a step on a single vibrator that plays a command on {@link VibratorController}. */
abstract class AbstractVibratorStep extends Step {
public final VibratorController controller;
- public final VibrationEffect.Composed effect;
- public final int segmentIndex;
long mVibratorOnResult;
long mPendingVibratorOffDeadline;
@@ -41,20 +36,15 @@
* @param startTime The time to schedule this step in the
* {@link VibrationStepConductor}.
* @param controller The vibrator that is playing the effect.
- * @param effect The effect being played in this step.
- * @param index The index of the next segment to be played by this step
* @param pendingVibratorOffDeadline The time the vibrator is expected to complete any
* previous vibration and turn off. This is used to allow this step to
* be triggered when the completion callback is received, and can
* be used to play effects back-to-back.
*/
AbstractVibratorStep(VibrationStepConductor conductor, long startTime,
- VibratorController controller, VibrationEffect.Composed effect, int index,
- long pendingVibratorOffDeadline) {
+ VibratorController controller, long pendingVibratorOffDeadline) {
super(conductor, startTime);
this.controller = controller;
- this.effect = effect;
- this.segmentIndex = index;
mPendingVibratorOffDeadline = pendingVibratorOffDeadline;
}
@@ -88,6 +78,7 @@
return shouldAcceptCallback;
}
+ @NonNull
@Override
public List<Step> cancel() {
return Arrays.asList(new CompleteEffectVibratorStep(conductor, SystemClock.uptimeMillis(),
@@ -138,43 +129,4 @@
controller.setAmplitude(amplitude);
getVibration().stats.reportSetAmplitude();
}
-
- /**
- * Return the {@link VibrationStepConductor#nextVibrateStep} with start and off timings
- * calculated from {@link #getVibratorOnDuration()} based on the current
- * {@link SystemClock#uptimeMillis()} and jumping all played segments from the effect.
- */
- protected List<Step> nextSteps(int segmentsPlayed) {
- // Schedule next steps to run right away.
- long nextStartTime = SystemClock.uptimeMillis();
- if (mVibratorOnResult > 0) {
- // Vibrator was turned on by this step, with mVibratorOnResult as the duration.
- // Schedule next steps for right after the vibration finishes.
- nextStartTime += mVibratorOnResult;
- }
- return nextSteps(nextStartTime, segmentsPlayed);
- }
-
- /**
- * Return the {@link VibrationStepConductor#nextVibrateStep} with given start time,
- * which might be calculated independently, and jumping all played segments from the effect.
- *
- * <p>This should be used when the vibrator on/off state is not responsible for the step
- * execution timing, e.g. while playing the vibrator amplitudes.
- */
- protected List<Step> nextSteps(long nextStartTime, int segmentsPlayed) {
- int nextSegmentIndex = segmentIndex + segmentsPlayed;
- int effectSize = effect.getSegments().size();
- int repeatIndex = effect.getRepeatIndex();
- if (nextSegmentIndex >= effectSize && repeatIndex >= 0) {
- // Count the loops that were played.
- int loopSize = effectSize - repeatIndex;
- int loopSegmentsPlayed = nextSegmentIndex - repeatIndex;
- getVibration().stats.reportRepetition(loopSegmentsPlayed / loopSize);
- nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize);
- }
- Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
- nextSegmentIndex, mPendingVibratorOffDeadline);
- return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep);
- }
}
diff --git a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
index 48dd992..7f9c349 100644
--- a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationEffect;
@@ -35,8 +36,7 @@
CompleteEffectVibratorStep(VibrationStepConductor conductor, long startTime, boolean cancelled,
VibratorController controller, long pendingVibratorOffDeadline) {
- super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
- pendingVibratorOffDeadline);
+ super(conductor, startTime, controller, pendingVibratorOffDeadline);
mCancelled = cancelled;
}
@@ -47,6 +47,7 @@
return mCancelled;
}
+ @NonNull
@Override
public List<Step> cancel() {
if (mCancelled) {
@@ -57,6 +58,7 @@
return super.cancel();
}
+ @NonNull
@Override
public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "CompleteEffectVibratorStep");
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
index 940bd08..e495af5 100644
--- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.os.Trace;
import android.os.VibrationEffect;
import android.os.vibrator.PrimitiveSegment;
@@ -31,7 +32,7 @@
* <p>This step will use the maximum supported number of consecutive segments of type
* {@link PrimitiveSegment} starting at the current index.
*/
-final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
+final class ComposePrimitivesVibratorStep extends AbstractComposedVibratorStep {
/**
* Default limit to the number of primitives in a composition, if none is defined by the HAL,
* to prevent repeating effects from generating an infinite list.
@@ -47,6 +48,7 @@
index, pendingVibratorOffDeadline);
}
+ @NonNull
@Override
public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep");
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
index 5d572be6..e8952fa 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.os.Trace;
import android.os.VibrationEffect;
import android.os.vibrator.RampSegment;
@@ -31,7 +32,7 @@
* <p>This step will use the maximum supported number of consecutive segments of type
* {@link RampSegment}, starting at the current index.
*/
-final class ComposePwleVibratorStep extends AbstractVibratorStep {
+final class ComposePwleVibratorStep extends AbstractComposedVibratorStep {
/**
* Default limit to the number of PWLE segments, if none is defined by the HAL, to prevent
* repeating effects from generating an infinite list.
@@ -47,6 +48,7 @@
index, pendingVibratorOffDeadline);
}
+ @NonNull
@Override
public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep");
diff --git a/services/core/java/com/android/server/vibrator/DeviceAdapter.java b/services/core/java/com/android/server/vibrator/DeviceAdapter.java
index 98309cd..bd4fc07 100644
--- a/services/core/java/com/android/server/vibrator/DeviceAdapter.java
+++ b/services/core/java/com/android/server/vibrator/DeviceAdapter.java
@@ -21,7 +21,6 @@
import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.VibrationEffectSegment;
-import android.util.Slog;
import android.util.SparseArray;
import java.util.ArrayList;
@@ -82,9 +81,8 @@
@NonNull
@Override
public VibrationEffect adaptToVibrator(int vibratorId, @NonNull VibrationEffect effect) {
- if (!(effect instanceof VibrationEffect.Composed)) {
+ if (!(effect instanceof VibrationEffect.Composed composed)) {
// Segments adapters can only apply to Composed effects.
- Slog.wtf(TAG, "Error adapting unsupported vibration effect: " + effect);
return effect;
}
@@ -95,7 +93,6 @@
}
VibratorInfo info = controller.getVibratorInfo();
- VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
List<VibrationEffectSegment> newSegments = new ArrayList<>(composed.getSegments());
int newRepeatIndex = composed.getRepeatIndex();
diff --git a/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
index c9683d9..6456371 100644
--- a/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
+++ b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.os.Trace;
import android.util.Slog;
@@ -43,6 +44,7 @@
return true;
}
+ @NonNull
@Override
public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishSequentialEffectStep");
@@ -61,6 +63,7 @@
}
}
+ @NonNull
@Override
public List<Step> cancel() {
cancelImmediately();
diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
index 8094e7c5..4b23216 100644
--- a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.os.Trace;
import android.os.VibrationEffect;
import android.os.vibrator.PrebakedSegment;
@@ -31,7 +32,7 @@
* <p>This step automatically falls back by replacing the prebaked segment with
* {@link VibrationSettings#getFallbackEffect(int)}, if available.
*/
-final class PerformPrebakedVibratorStep extends AbstractVibratorStep {
+final class PerformPrebakedVibratorStep extends AbstractComposedVibratorStep {
PerformPrebakedVibratorStep(VibrationStepConductor conductor, long startTime,
VibratorController controller, VibrationEffect.Composed effect, int index,
@@ -42,6 +43,7 @@
index, pendingVibratorOffDeadline);
}
+ @NonNull
@Override
public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformPrebakedVibratorStep");
diff --git a/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
new file mode 100644
index 0000000..8f36118
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.NonNull;
+import android.os.Trace;
+import android.os.VibrationEffect;
+
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on with a vendor-specific vibration from a
+ * {@link VibrationEffect.VendorEffect} effect.
+ */
+final class PerformVendorEffectVibratorStep extends AbstractVibratorStep {
+ /**
+ * Timeout to ensure vendor vibrations are not unbounded if vibrator callbacks are lost.
+ */
+ static final long VENDOR_EFFECT_MAX_DURATION_MS = 60_000; // 1 min
+
+ public final VibrationEffect.VendorEffect effect;
+
+ PerformVendorEffectVibratorStep(VibrationStepConductor conductor, long startTime,
+ VibratorController controller, VibrationEffect.VendorEffect effect,
+ long pendingVibratorOffDeadline) {
+ // This step should wait for the last vibration to finish (with the timeout) and for the
+ // intended step start time (to respect the effect delays).
+ super(conductor, Math.max(startTime, pendingVibratorOffDeadline), controller,
+ pendingVibratorOffDeadline);
+ this.effect = effect;
+ }
+
+ @NonNull
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformVendorEffectVibratorStep");
+ try {
+ long vibratorOnResult = controller.on(effect, getVibration().id);
+ vibratorOnResult = Math.min(vibratorOnResult, VENDOR_EFFECT_MAX_DURATION_MS);
+ handleVibratorOnResult(vibratorOnResult);
+ return List.of(new CompleteEffectVibratorStep(conductor, startTime,
+ /* cancelled= */ false, controller, mPendingVibratorOffDeadline));
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
index f40c994..901f9c3 100644
--- a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.os.SystemClock;
import android.os.Trace;
import android.util.Slog;
@@ -31,8 +32,7 @@
RampOffVibratorStep(VibrationStepConductor conductor, long startTime, float amplitudeTarget,
float amplitudeDelta, VibratorController controller,
long pendingVibratorOffDeadline) {
- super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
- pendingVibratorOffDeadline);
+ super(conductor, startTime, controller, pendingVibratorOffDeadline);
mAmplitudeTarget = amplitudeTarget;
mAmplitudeDelta = amplitudeDelta;
}
@@ -42,12 +42,14 @@
return true;
}
+ @NonNull
@Override
public List<Step> cancel() {
return Arrays.asList(new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(),
controller, /* isCleanUp= */ true));
}
+ @NonNull
@Override
public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "RampOffVibratorStep");
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index e13ec6c..8478e77 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationEffect;
@@ -32,7 +33,7 @@
* <p>This step ignores vibration completion callbacks and control the vibrator on/off state
* and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}.
*/
-final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
+final class SetAmplitudeVibratorStep extends AbstractComposedVibratorStep {
/**
* The repeating waveform keeps the vibrator ON all the time. Use a minimum duration to
* prevent short patterns from turning the vibrator ON too frequently.
@@ -69,6 +70,7 @@
return shouldAcceptCallback;
}
+ @NonNull
@Override
public List<Step> play() {
// TODO: consider separating the "on" steps at the start into a separate Step.
diff --git a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
index c197271..3ceba57 100644
--- a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
+++ b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.vibrator.IVibratorManager;
import android.os.CombinedVibration;
@@ -74,6 +75,7 @@
return mVibratorsOnMaxDuration;
}
+ @NonNull
@Override
public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartSequentialEffectStep");
@@ -111,6 +113,7 @@
return nextSteps;
}
+ @NonNull
@Override
public List<Step> cancel() {
return VibrationStepConductor.EMPTY_STEP_LIST;
@@ -173,13 +176,12 @@
for (int i = 0; i < vibratorCount; i++) {
steps[i] = conductor.nextVibrateStep(vibrationStartTime,
conductor.getVibrators().get(effectMapping.vibratorIdAt(i)),
- effectMapping.effectAt(i),
- /* segmentIndex= */ 0, /* vibratorOffTimeout= */ 0);
+ effectMapping.effectAt(i));
}
if (steps.length == 1) {
// No need to prepare and trigger sync effects on a single vibrator.
- return startVibrating(steps[0], nextSteps);
+ return startVibrating(steps[0], effectMapping.effectAt(0), nextSteps);
}
// This synchronization of vibrators should be executed one at a time, even if we are
@@ -196,8 +198,8 @@
effectMapping.getRequiredSyncCapabilities(),
effectMapping.getVibratorIds());
- for (AbstractVibratorStep step : steps) {
- long duration = startVibrating(step, nextSteps);
+ for (int i = 0; i < vibratorCount; i++) {
+ long duration = startVibrating(steps[i], effectMapping.effectAt(i), nextSteps);
if (duration < 0) {
// One vibrator has failed, fail this entire sync attempt.
hasFailed = true;
@@ -231,7 +233,12 @@
return hasFailed ? -1 : maxDuration;
}
- private long startVibrating(AbstractVibratorStep step, List<Step> nextSteps) {
+ private long startVibrating(@Nullable AbstractVibratorStep step, VibrationEffect effect,
+ List<Step> nextSteps) {
+ if (step == null) {
+ // Failed to create a step for VibrationEffect.
+ return -1;
+ }
nextSteps.addAll(step.play());
long stepDuration = step.getVibratorOnDuration();
if (stepDuration < 0) {
@@ -239,7 +246,7 @@
return stepDuration;
}
// Return the longest estimation for the entire effect.
- return Math.max(stepDuration, step.effect.getDuration());
+ return Math.max(stepDuration, effect.getDuration());
}
/**
@@ -249,28 +256,20 @@
* play all of the effects in sync.
*/
final class DeviceEffectMap {
- private final SparseArray<VibrationEffect.Composed> mVibratorEffects;
+ private final SparseArray<VibrationEffect> mVibratorEffects;
private final int[] mVibratorIds;
private final long mRequiredSyncCapabilities;
DeviceEffectMap(CombinedVibration.Mono mono) {
SparseArray<VibratorController> vibrators = conductor.getVibrators();
VibrationEffect effect = mono.getEffect();
- if (effect instanceof VibrationEffect.Composed) {
- mVibratorEffects = new SparseArray<>(vibrators.size());
- mVibratorIds = new int[vibrators.size()];
+ mVibratorEffects = new SparseArray<>(vibrators.size());
+ mVibratorIds = new int[vibrators.size()];
- VibrationEffect.Composed composedEffect = (VibrationEffect.Composed) effect;
- for (int i = 0; i < vibrators.size(); i++) {
- int vibratorId = vibrators.keyAt(i);
- mVibratorEffects.put(vibratorId, composedEffect);
- mVibratorIds[i] = vibratorId;
- }
- } else {
- Slog.wtf(VibrationThread.TAG,
- "Unable to map device vibrators to unexpected effect: " + effect);
- mVibratorEffects = new SparseArray<>();
- mVibratorIds = new int[0];
+ for (int i = 0; i < vibrators.size(); i++) {
+ int vibratorId = vibrators.keyAt(i);
+ mVibratorEffects.put(vibratorId, effect);
+ mVibratorIds[i] = vibratorId;
}
mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
}
@@ -282,13 +281,7 @@
for (int i = 0; i < stereoEffects.size(); i++) {
int vibratorId = stereoEffects.keyAt(i);
if (vibrators.contains(vibratorId)) {
- VibrationEffect effect = stereoEffects.valueAt(i);
- if (effect instanceof VibrationEffect.Composed) {
- mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
- } else {
- Slog.wtf(VibrationThread.TAG,
- "Unable to map device vibrators to unexpected effect: " + effect);
- }
+ mVibratorEffects.put(vibratorId, stereoEffects.valueAt(i));
}
}
mVibratorIds = new int[mVibratorEffects.size()];
@@ -326,7 +319,7 @@
}
/** Return the {@link VibrationEffect} at given index. */
- public VibrationEffect.Composed effectAt(int index) {
+ public VibrationEffect effectAt(int index) {
return mVibratorEffects.valueAt(index);
}
@@ -338,16 +331,24 @@
* IVibratorManager.CAP_PREPARE_* and IVibratorManager.CAP_MIXED_TRIGGER_* capabilities.
*/
private long calculateRequiredSyncCapabilities(
- SparseArray<VibrationEffect.Composed> effects) {
+ SparseArray<VibrationEffect> effects) {
long prepareCap = 0;
for (int i = 0; i < effects.size(); i++) {
- VibrationEffectSegment firstSegment = effects.valueAt(i).getSegments().get(0);
- if (firstSegment instanceof StepSegment) {
- prepareCap |= IVibratorManager.CAP_PREPARE_ON;
- } else if (firstSegment instanceof PrebakedSegment) {
+ VibrationEffect effect = effects.valueAt(i);
+ if (effect instanceof VibrationEffect.VendorEffect) {
prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
- } else if (firstSegment instanceof PrimitiveSegment) {
- prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
+ } else if (effect instanceof VibrationEffect.Composed composed) {
+ VibrationEffectSegment firstSegment = composed.getSegments().get(0);
+ if (firstSegment instanceof StepSegment) {
+ prepareCap |= IVibratorManager.CAP_PREPARE_ON;
+ } else if (firstSegment instanceof PrebakedSegment) {
+ prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
+ } else if (firstSegment instanceof PrimitiveSegment) {
+ prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
+ }
+ } else {
+ Slog.wtf(VibrationThread.TAG,
+ "Unable to check sync capabilities to unexpected effect: " + effect);
}
}
int triggerCap = 0;
diff --git a/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
index 065ce11..87dc269 100644
--- a/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.os.SystemClock;
import android.os.Trace;
@@ -36,7 +37,7 @@
TurnOffVibratorStep(VibrationStepConductor conductor, long startTime,
VibratorController controller, boolean isCleanUp) {
- super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1, startTime);
+ super(conductor, startTime, controller, startTime);
mIsCleanUp = isCleanUp;
}
@@ -45,6 +46,7 @@
return mIsCleanUp;
}
+ @NonNull
@Override
public List<Step> cancel() {
return Arrays.asList(new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(),
@@ -56,6 +58,7 @@
stopVibrating();
}
+ @NonNull
@Override
public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "TurnOffVibratorStep");
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 689b495..5c567da 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -393,13 +393,14 @@
private void dumpEffect(
ProtoOutputStream proto, long fieldId, VibrationEffect effect) {
- final long token = proto.start(fieldId);
- VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
- for (VibrationEffectSegment segment : composed.getSegments()) {
- dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment);
+ if (effect instanceof VibrationEffect.Composed composed) {
+ final long token = proto.start(fieldId);
+ for (VibrationEffectSegment segment : composed.getSegments()) {
+ dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment);
+ }
+ proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex());
+ proto.end(token);
}
- proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex());
- proto.end(token);
}
private void dumpEffect(ProtoOutputStream proto, long fieldId,
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index d9ca710..3933759 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -25,14 +25,12 @@
import android.os.Vibrator;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.VibrationEffectSegment;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Locale;
/** Controls vibration scaling. */
@@ -136,12 +134,6 @@
*/
@NonNull
public VibrationEffect scale(@NonNull VibrationEffect effect, int usageHint) {
- if (!(effect instanceof VibrationEffect.Composed)) {
- // This only scales composed vibration effects.
- Slog.wtf(TAG, "Error scaling unsupported vibration effect: " + effect);
- return effect;
- }
-
int newEffectStrength = getEffectStrength(usageHint);
ScaleLevel scaleLevel = mScaleLevels.get(getScaleLevel(usageHint));
float adaptiveScale = getAdaptiveHapticsScale(usageHint);
@@ -154,26 +146,10 @@
scaleLevel = SCALE_LEVEL_NONE;
}
- VibrationEffect.Composed composedEffect = (VibrationEffect.Composed) effect;
- ArrayList<VibrationEffectSegment> segments =
- new ArrayList<>(composedEffect.getSegments());
- int segmentCount = segments.size();
- for (int i = 0; i < segmentCount; i++) {
- segments.set(i,
- segments.get(i).resolve(mDefaultVibrationAmplitude)
- .applyEffectStrength(newEffectStrength)
- .scale(scaleLevel.factor)
- .scaleLinearly(adaptiveScale));
- }
- if (segments.equals(composedEffect.getSegments())) {
- // No segment was updated, return original effect.
- return effect;
- }
- VibrationEffect.Composed scaled =
- new VibrationEffect.Composed(segments, composedEffect.getRepeatIndex());
- // Make sure we validate what was scaled, since we're using the constructor directly
- scaled.validate();
- return scaled;
+ return effect.resolve(mDefaultVibrationAmplitude)
+ .applyEffectStrength(newEffectStrength)
+ .scale(scaleLevel.factor)
+ .scaleLinearly(adaptiveScale);
}
/**
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index f3e226e..8c9a92d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -123,6 +123,24 @@
@Nullable
AbstractVibratorStep nextVibrateStep(long startTime, VibratorController controller,
+ VibrationEffect effect) {
+ if (Build.IS_DEBUGGABLE) {
+ expectIsVibrationThread(true);
+ }
+ if (effect instanceof VibrationEffect.VendorEffect vendorEffect) {
+ return new PerformVendorEffectVibratorStep(this, startTime, controller, vendorEffect,
+ /* pendingVibratorOffDeadline= */ 0);
+ }
+ if (effect instanceof VibrationEffect.Composed composed) {
+ return nextVibrateStep(startTime, controller, composed, /* segmentIndex= */ 0,
+ /* pendingVibratorOffDeadline= */ 0);
+ }
+ Slog.wtf(TAG, "Unable to create next step for unexpected effect: " + effect);
+ return null;
+ }
+
+ @NonNull
+ AbstractVibratorStep nextVibrateStep(long startTime, VibratorController controller,
VibrationEffect.Composed effect, int segmentIndex, long pendingVibratorOffDeadline) {
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(true);
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 988e8fe..8cc157c 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -20,8 +20,10 @@
import android.hardware.vibrator.IVibrator;
import android.os.Binder;
import android.os.IVibratorStateListener;
+import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
@@ -262,6 +264,35 @@
}
/**
+ * Plays vendor vibration effect, using {@code vibrationId} for completion callback to
+ * {@link OnVibrationCompleteListener}.
+ *
+ * <p>This will affect the state of {@link #isVibrating()}.
+ *
+ * @return The positive duration of the vibration started, if successful, zero if the vibrator
+ * do not support the input or a negative number if the operation failed.
+ */
+ public long on(VibrationEffect.VendorEffect vendorEffect, long vibrationId) {
+ synchronized (mLock) {
+ Parcel vendorData = Parcel.obtain();
+ try {
+ vendorEffect.getVendorData().writeToParcel(vendorData, /* flags= */ 0);
+ vendorData.setDataPosition(0);
+ long duration = mNativeWrapper.performVendorEffect(vendorData,
+ vendorEffect.getEffectStrength(), vendorEffect.getLinearScale(),
+ vibrationId);
+ if (duration > 0) {
+ mCurrentAmplitude = -1;
+ notifyListenerOnVibrating(true);
+ }
+ return duration;
+ } finally {
+ vendorData.recycle();
+ }
+ }
+ }
+
+ /**
* Plays predefined vibration effect, using {@code vibrationId} for completion callback to
* {@link OnVibrationCompleteListener}.
*
@@ -427,6 +458,9 @@
private static native long performEffect(long nativePtr, long effect, long strength,
long vibrationId);
+ private static native long performVendorEffect(long nativePtr, Parcel vendorData,
+ long strength, float scale, long vibrationId);
+
private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect,
long vibrationId);
@@ -482,6 +516,12 @@
return performEffect(mNativePtr, effect, strength, vibrationId);
}
+ /** Turns vibrator on to perform a vendor-specific effect. */
+ public long performVendorEffect(Parcel vendorData, long strength, float scale,
+ long vibrationId) {
+ return performVendorEffect(mNativePtr, vendorData, strength, scale, vibrationId);
+ }
+
/** Turns vibrator on to perform effect composed of give primitives effect. */
public long compose(PrimitiveSegment[] primitives, long vibrationId) {
return performComposedEffect(mNativePtr, primitives, vibrationId);
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index bff175f..48c4a68 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -540,6 +540,11 @@
Slog.e(TAG, "token must not be null");
return null;
}
+ if (effect.hasVendorEffects()
+ && !hasPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)) {
+ Slog.w(TAG, "vibrate; no permission for vendor effects");
+ return null;
+ }
enforceUpdateAppOpsStatsPermission(uid);
if (!isEffectValid(effect)) {
return null;
@@ -1304,12 +1309,13 @@
}
private void fillVibrationFallbacks(HalVibration vib, VibrationEffect effect) {
- VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ if (!(effect instanceof VibrationEffect.Composed composed)) {
+ return;
+ }
int segmentCount = composed.getSegments().size();
for (int i = 0; i < segmentCount; i++) {
VibrationEffectSegment segment = composed.getSegments().get(i);
- if (segment instanceof PrebakedSegment) {
- PrebakedSegment prebaked = (PrebakedSegment) segment;
+ if (segment instanceof PrebakedSegment prebaked) {
VibrationEffect fallback = mVibrationSettings.getFallbackEffect(
prebaked.getEffectId());
if (prebaked.shouldFallback() && fallback != null) {
@@ -1392,12 +1398,11 @@
@Nullable
private static PrebakedSegment extractPrebakedSegment(VibrationEffect effect) {
- if (effect instanceof VibrationEffect.Composed) {
- VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ if (effect instanceof VibrationEffect.Composed composed) {
if (composed.getSegments().size() == 1) {
VibrationEffectSegment segment = composed.getSegments().get(0);
- if (segment instanceof PrebakedSegment) {
- return (PrebakedSegment) segment;
+ if (segment instanceof PrebakedSegment prebaked) {
+ return prebaked;
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4da7e53..d039b04 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -10890,12 +10890,8 @@
* Whether we should send fake focus when the activity is resumed. This is done because some
* game engines wait to get focus before drawing the content of the app.
*/
- // TODO(b/263593361): Explore enabling compat fake focus for freeform.
- // TODO(b/263592337): Explore enabling compat fake focus for fullscreen, e.g. for when
- // covered with bubbles.
boolean shouldSendCompatFakeFocus() {
- return mLetterboxUiController.shouldSendFakeFocus() && inMultiWindowMode()
- && !inPinnedWindowingMode() && !inFreeformWindowingMode();
+ return mAppCompatController.getAppCompatFocusOverrides().shouldSendFakeFocus();
}
boolean canCaptureSnapshot() {
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index f9e2507..998d65d 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -94,4 +94,9 @@
}
return null;
}
+
+ @NonNull
+ AppCompatFocusOverrides getAppCompatFocusOverrides() {
+ return mAppCompatOverrides.getAppCompatFocusOverrides();
+ }
}
diff --git a/services/core/java/com/android/server/wm/AppCompatFocusOverrides.java b/services/core/java/com/android/server/wm/AppCompatFocusOverrides.java
new file mode 100644
index 0000000..ab4bb14
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatFocusOverrides.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
+import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
+
+import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
+
+import android.annotation.NonNull;
+
+import com.android.server.wm.utils.OptPropFactory;
+
+/**
+ * Encapsulates app compat focus policy.
+ */
+class AppCompatFocusOverrides {
+
+ @NonNull
+ final ActivityRecord mActivityRecord;
+ @NonNull
+ private final OptPropFactory.OptProp mFakeFocusOptProp;
+
+ AppCompatFocusOverrides(@NonNull ActivityRecord activityRecord,
+ @NonNull AppCompatConfiguration appCompatConfiguration,
+ @NonNull OptPropFactory optPropBuilder) {
+ mActivityRecord = activityRecord;
+ mFakeFocusOptProp = optPropBuilder.create(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS,
+ appCompatConfiguration::isCompatFakeFocusEnabled);
+ }
+
+ /**
+ * Whether sending compat fake focus for split screen resumed activities is enabled. Needed
+ * because some game engines wait to get focus before drawing the content of the app which isn't
+ * guaranteed by default in multi-window modes.
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the treatment is enabled
+ * <li>Component property is NOT set to false
+ * <li>Component property is set to true or per-app override is enabled
+ * </ul>
+ */
+ boolean shouldSendFakeFocus() {
+ // TODO(b/263593361): Explore enabling compat fake focus for freeform.
+ // TODO(b/263592337): Explore enabling compat fake focus for fullscreen, e.g. for when
+ // covered with bubbles.
+ return mFakeFocusOptProp.shouldEnableWithOverrideAndProperty(
+ isChangeEnabled(mActivityRecord, OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS))
+ && mActivityRecord.inMultiWindowMode() && !mActivityRecord.inPinnedWindowingMode()
+ && !mActivityRecord.inFreeformWindowingMode();
+ }
+
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index b611ba9..cde48d6 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -18,17 +18,12 @@
import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
-import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
-import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
-
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.annotation.NonNull;
@@ -39,17 +34,10 @@
*/
public class AppCompatOverrides {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "AppCompatOverrides" : TAG_ATM;
-
- @NonNull
- private final AppCompatConfiguration mAppCompatConfiguration;
-
@NonNull
private final ActivityRecord mActivityRecord;
@NonNull
- private final OptPropFactory.OptProp mFakeFocusOptProp;
- @NonNull
private final OptPropFactory.OptProp mAllowOrientationOverrideOptProp;
@NonNull
private final OptPropFactory.OptProp mAllowDisplayOrientationOverrideOptProp;
@@ -61,26 +49,25 @@
private final AppCompatCameraOverrides mAppCompatCameraOverrides;
@NonNull
private final AppCompatAspectRatioOverrides mAppCompatAspectRatioOverrides;
+ @NonNull
+ private final AppCompatFocusOverrides mAppCompatFocusOverrides;
AppCompatOverrides(@NonNull ActivityRecord activityRecord,
@NonNull AppCompatConfiguration appCompatConfiguration,
@NonNull OptPropFactory optPropBuilder) {
- mAppCompatConfiguration = appCompatConfiguration;
mActivityRecord = activityRecord;
mAppCompatCameraOverrides = new AppCompatCameraOverrides(mActivityRecord,
- mAppCompatConfiguration, optPropBuilder);
+ appCompatConfiguration, optPropBuilder);
mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(mActivityRecord,
- mAppCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
+ appCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
// TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with reachability.
mAppCompatAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
- mAppCompatConfiguration, optPropBuilder,
+ appCompatConfiguration, optPropBuilder,
activityRecord.mLetterboxUiController::isDisplayFullScreenAndInPosture,
activityRecord.mLetterboxUiController::getHorizontalPositionMultiplier);
-
- mFakeFocusOptProp = optPropBuilder.create(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS,
- mAppCompatConfiguration::isCompatFakeFocusEnabled);
-
+ mAppCompatFocusOverrides = new AppCompatFocusOverrides(mActivityRecord,
+ appCompatConfiguration, optPropBuilder);
mAllowOrientationOverrideOptProp = optPropBuilder.create(
PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE);
@@ -114,21 +101,9 @@
return mAppCompatAspectRatioOverrides;
}
- /**
- * Whether sending compat fake focus for split screen resumed activities is enabled. Needed
- * because some game engines wait to get focus before drawing the content of the app which isn't
- * guaranteed by default in multi-window modes.
- *
- * <p>This treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Flag gating the treatment is enabled
- * <li>Component property is NOT set to false
- * <li>Component property is set to true or per-app override is enabled
- * </ul>
- */
- boolean shouldSendFakeFocus() {
- return mFakeFocusOptProp.shouldEnableWithOverrideAndProperty(
- isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS));
+ @NonNull
+ AppCompatFocusOverrides getAppCompatFocusOverrides() {
+ return mAppCompatFocusOverrides;
}
boolean isAllowOrientationOverrideOptOut() {
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 1b30a20..fd816067 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -73,4 +73,13 @@
static boolean isInVrUiMode(Configuration config) {
return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET;
}
+
+ /**
+ * @param activityRecord The {@link ActivityRecord} for the app package.
+ * @param overrideChangeId The per-app override identifier.
+ * @return {@code true} if the per-app override is enable for the given activity.
+ */
+ static boolean isChangeEnabled(@NonNull ActivityRecord activityRecord, long overrideChangeId) {
+ return activityRecord.info.isChangeEnabled(overrideChangeId);
+ }
}
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index 3ecdff6..cbb210f 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -27,6 +27,7 @@
import static com.android.server.wm.LaunchParamsUtil.applyLayoutGravity;
import static com.android.server.wm.LaunchParamsUtil.calculateLayoutBounds;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
@@ -41,7 +42,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
+import com.android.server.wm.utils.DesktopModeFlagsUtil;
import java.util.function.Consumer;
@@ -106,7 +107,7 @@
final TaskDisplayArea displayArea = task.getDisplayArea();
final Rect screenBounds = displayArea.getBounds();
final Size idealSize = calculateIdealSize(screenBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
- if (!Flags.enableWindowingDynamicInitialBounds()) {
+ if (!DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(activity.mWmService.mContext)) {
return centerInScreen(idealSize, screenBounds);
}
// TODO(b/353457301): Replace with app compat aspect ratio method when refactoring complete.
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 7c31177..2d1eb41 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -28,7 +28,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
+
+/**
+ * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
+ * black layers of varying opacity at various Z-levels which create the effect of a Dim.
+ */
class Dimmer {
/**
@@ -122,8 +128,10 @@
/**
* Set the parameters to prepare the dim to be relative parented to the dimming container
*/
- void prepareReparent(@NonNull WindowContainer<?> relativeParent, int relativeLayer) {
+ void prepareReparent(@NonNull WindowContainer<?> geometryParent,
+ @NonNull WindowContainer<?> relativeParent, int relativeLayer) {
mAnimationHelper.setRequestedRelativeParent(relativeParent, relativeLayer);
+ mAnimationHelper.setRequestedGeometryParent(geometryParent);
}
/**
@@ -138,7 +146,8 @@
* Whether anyone is currently requesting the dim
*/
boolean isDimming() {
- return mLastRequestedDimContainer != null;
+ return mLastRequestedDimContainer != null
+ && (mHostContainer.isVisibleRequested() || !Flags.useTasksDimOnly());
}
private SurfaceControl makeDimLayer() {
@@ -208,13 +217,15 @@
* the child of the host should call adjustRelativeLayer and {@link Dimmer#adjustAppearance} to
* continue dimming. Indeed, this method won't be able to keep dimming or get a new DimState
* without also adjusting the appearance.
+ * @param geometryParent The container that defines the geometry of the dim
* @param dimmingContainer The container which to dim above. Should be a child of the host.
* @param relativeLayer The position of the dim wrt the container
*/
- public void adjustRelativeLayer(@NonNull WindowContainer<?> dimmingContainer,
+ public void adjustPosition(@NonNull WindowContainer<?> geometryParent,
+ @NonNull WindowContainer<?> dimmingContainer,
int relativeLayer) {
if (mDimState != null) {
- mDimState.prepareReparent(dimmingContainer, relativeLayer);
+ mDimState.prepareReparent(geometryParent, dimmingContainer, relativeLayer);
}
}
@@ -236,7 +247,9 @@
return false;
} else {
// Someone is dimming, show the requested changes
- mDimState.adjustSurfaceLayout(t);
+ if (!Flags.useTasksDimOnly()) {
+ mDimState.adjustSurfaceLayout(t);
+ }
final WindowState ws = mDimState.mLastRequestedDimContainer.asWindowState();
if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
&& ws.mActivityRecord.mStartingData != null) {
@@ -263,6 +276,7 @@
return mDimState != null ? mDimState.mDimSurface : null;
}
+ @Deprecated
Rect getDimBounds() {
return mDimState != null ? mDimState.mDimBounds : null;
}
diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
index df1549e..3dba57f 100644
--- a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
+++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
@@ -26,6 +26,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
@@ -48,6 +49,7 @@
private float mAlpha = -1f;
private int mBlurRadius = -1;
private WindowContainer<?> mDimmingContainer = null;
+ private WindowContainer<?> mGeometryParent = null;
private int mRelativeLayer = -1;
private static final float EPSILON = 0.0001f;
@@ -103,6 +105,11 @@
mRequestedProperties.mRelativeLayer = relativeLayer;
}
+ // Sets the requested layer to reparent the dim to without applying it immediately
+ void setRequestedGeometryParent(WindowContainer<?> geometryParent) {
+ mRequestedProperties.mGeometryParent = geometryParent;
+ }
+
// Sets a requested change without applying it immediately
void setRequestedAppearance(float alpha, int blurRadius) {
mRequestedProperties.mAlpha = alpha;
@@ -129,7 +136,9 @@
}
dim.ensureVisible(t);
- relativeReparent(dim.mDimSurface,
+ reparent(dim.mDimSurface,
+ mRequestedProperties.mGeometryParent != mCurrentProperties.mGeometryParent
+ ? mRequestedProperties.mGeometryParent.getSurfaceControl() : null,
mRequestedProperties.mDimmingContainer.getSurfaceControl(),
mRequestedProperties.mRelativeLayer, t);
@@ -214,11 +223,17 @@
}
/**
- * Change the relative parent of this dim layer
+ * Change the geometry and relative parent of this dim layer
*/
- void relativeReparent(@NonNull SurfaceControl dimLayer, @NonNull SurfaceControl relativeParent,
- int relativePosition, @NonNull SurfaceControl.Transaction t) {
+ void reparent(@NonNull SurfaceControl dimLayer,
+ @Nullable SurfaceControl newGeometryParent,
+ @NonNull SurfaceControl relativeParent,
+ int relativePosition,
+ @NonNull SurfaceControl.Transaction t) {
try {
+ if (newGeometryParent != null) {
+ t.reparent(dimLayer, newGeometryParent);
+ }
t.setRelativeLayer(dimLayer, relativeParent, relativePosition);
} catch (NullPointerException e) {
Log.w(TAG, "Tried to change parent of dim " + dimLayer + " after remove", e);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index be8e806..73f3655 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -117,22 +117,6 @@
}
/**
- * Whether sending compat fake focus for split screen resumed activities is enabled. Needed
- * because some game engines wait to get focus before drawing the content of the app which isn't
- * guaranteed by default in multi-window modes.
- *
- * <p>This treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Flag gating the treatment is enabled
- * <li>Component property is NOT set to false
- * <li>Component property is set to true or per-app override is enabled
- * </ul>
- */
- boolean shouldSendFakeFocus() {
- return getAppCompatOverrides().shouldSendFakeFocus();
- }
-
- /**
* Whether we should apply the force resize per-app override. When this override is applied it
* forces the packages it is applied to to be resizable. It won't change whether the app can be
* put into multi-windowing mode, but allow the app to resize without going into size-compat
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 259ca83..74adeb9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3201,6 +3201,14 @@
return "Task=" + mTaskId;
}
+ WindowContainer<?> getDimmerParent() {
+ if (!inMultiWindowMode() && isTranslucentForTransition()) {
+ return getRootDisplayArea();
+ }
+ return this;
+ }
+
+ @Deprecated
@Override
Dimmer getDimmer() {
// If the window is in multi-window mode, we want to dim at the Task level to ensure the dim
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 750394d..c83b280 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -3102,6 +3102,7 @@
return forAllWindows(getDimBehindWindow, true);
}
+ @Deprecated
@Override
Dimmer getDimmer() {
// If this is in an embedded TaskFragment and we want the dim applies on the TaskFragment.
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 57c7753..235deb2 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -232,8 +232,14 @@
if (imeWindow != null && imeWindow.isVisible()) {
final Rect bounds = imeWindow.getParentFrame();
bounds.offsetTo(0, 0);
- imeBuffer = ScreenCapture.captureLayersExcluding(imeWindow.getSurfaceControl(),
- bounds, 1.0f, pixelFormat, null);
+ ScreenCapture.LayerCaptureArgs captureArgs = new ScreenCapture.LayerCaptureArgs.Builder(
+ imeWindow.getSurfaceControl())
+ .setSourceCrop(bounds)
+ .setFrameScale(1.0f)
+ .setPixelFormat(pixelFormat)
+ .setCaptureSecureLayers(true)
+ .build();
+ imeBuffer = ScreenCapture.captureLayers(captureArgs);
}
return imeBuffer;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index cf5a1e6..358adc3 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -463,14 +463,26 @@
boolean canApplyDim(@NonNull Task task) {
if (mTransientLaunches == null) return true;
- final Dimmer dimmer = task.getDimmer();
- if (dimmer == null) {
- return false;
- }
- if (dimmer.hostIsTask()) {
+ if (Flags.useTasksDimOnly()) {
+ WindowContainer<?> dimmerParent = task.getDimmerParent();
+ if (dimmerParent == null) {
+ return false;
+ }
// Always allow to dim if the host only affects its task.
- return true;
+ if (dimmerParent.asTask() == task) {
+ return true;
+ }
+ } else {
+ final Dimmer dimmer = task.getDimmer();
+ if (dimmer == null) {
+ return false;
+ }
+ if (dimmer.hostIsTask()) {
+ // Always allow to dim if the host only affects its task.
+ return true;
+ }
}
+
// The dimmer host of a translucent task can be a display, then it is not in transient-hide.
for (int i = mTransientLaunches.size() - 1; i >= 0; --i) {
// The transient task is usually the task of recents/home activity.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index eb1a80b..64f9c01 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -107,11 +107,10 @@
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
-import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.wm.SurfaceAnimator.Animatable;
import com.android.server.wm.SurfaceAnimator.AnimationType;
@@ -3765,6 +3764,7 @@
}, true /* traverseTopToBottom */);
}
+ @Deprecated
Dimmer getDimmer() {
if (mParent == null) {
return null;
diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java
index e98b1a2..87e120c 100644
--- a/services/core/java/com/android/server/wm/WindowContextListenerController.java
+++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java
@@ -356,7 +356,7 @@
}
}
mDeathRecipient.unlinkToDeath();
- mWpc.scheduleClientTransactionItem(WindowContextWindowRemovalItem.obtain(mClientToken));
+ mWpc.scheduleClientTransactionItem(new WindowContextWindowRemovalItem(mClientToken));
unregister();
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 240978a..164994c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -103,6 +103,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
@@ -5195,9 +5196,10 @@
}
private void applyDims() {
+ Task task = getTask();
if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
&& mWinAnimator.getShown()
- && !mHidden && mTransitionController.canApplyDim(getTask())) {
+ && !mHidden && mTransitionController.canApplyDim(task)) {
// Only show the Dimmer when the following is satisfied:
// 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested
// 2. The WindowToken is not hidden so dims aren't shown when the window is exiting.
@@ -5210,10 +5212,31 @@
// If the window is visible from surface flinger perspective (mWinAnimator.getShown())
// but not window manager visible (!isVisibleNow()), it can still be the parent of the
// dim, but can not create a new surface or continue a dim alone.
- if (isVisibleNow()) {
- getDimmer().adjustAppearance(this, dimAmount, blurRadius);
+ Dimmer dimmer;
+ WindowContainer<?> geometryParent = task;
+ if (Flags.useTasksDimOnly()) {
+ if (task != null) {
+ geometryParent = task.getDimmerParent();
+ dimmer = task.mDimmer;
+ } else {
+ RootDisplayArea displayArea = getRootDisplayArea();
+ geometryParent = displayArea;
+ dimmer = displayArea != null ? displayArea.getDimmer() : null;
+ }
+ if (dimmer == null) {
+ ProtoLog.e(WM_DEBUG_DIMMER, "WindowState %s does not have task or"
+ + " display area for dimming", this);
+ return;
+ }
+ } else {
+ dimmer = getDimmer();
}
- getDimmer().adjustRelativeLayer(this, -1 /* relativeLayer */);
+
+ if (isVisibleNow()) {
+ dimmer.adjustAppearance(this, dimAmount, blurRadius);
+ }
+ dimmer.adjustPosition(geometryParent,
+ this /* relativeParent */, -1 /* relativeLayer */);
}
}
diff --git a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java b/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
index 3559e62..70c66de 100644
--- a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
+++ b/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
@@ -43,8 +43,7 @@
// All desktop mode related flags to be overridden by developer option toggle will be added here
DESKTOP_WINDOWING_MODE(
Flags::enableDesktopWindowingMode, /* shouldOverrideByDevOption= */ true),
- WALLPAPER_ACTIVITY(
- Flags::enableDesktopWindowingWallpaperActivity, /* shouldOverrideByDevOption= */ true);
+ DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true);
private static final String TAG = "DesktopModeFlagsUtil";
// Function called to obtain aconfig flag value.
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 3cd5f76..9fa1a53 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -193,7 +193,7 @@
"android.hardware.thermal-V2-ndk",
"android.hardware.tv.input@1.0",
"android.hardware.tv.input-V2-ndk",
- "android.hardware.vibrator-V2-ndk",
+ "android.hardware.vibrator-V3-ndk",
"android.hardware.vibrator@1.0",
"android.hardware.vibrator@1.1",
"android.hardware.vibrator@1.2",
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 2804a10..f12930a 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -17,7 +17,10 @@
#define LOG_TAG "VibratorController"
#include <aidl/android/hardware/vibrator/IVibrator.h>
+#include <android/binder_parcel.h>
+#include <android/binder_parcel_jni.h>
#include <android/hardware/vibrator/1.3/IVibrator.h>
+#include <android/persistable_bundle_aidl.h>
#include <nativehelper/JNIHelp.h>
#include <utils/Log.h>
#include <utils/misc.h>
@@ -32,6 +35,8 @@
namespace V1_3 = android::hardware::vibrator::V1_3;
namespace Aidl = aidl::android::hardware::vibrator;
+using aidl::android::os::PersistableBundle;
+
namespace android {
static JavaVM* sJvm = nullptr;
@@ -95,7 +100,7 @@
return nullptr;
}
auto result = manager->getVibrator(vibratorId);
- return result.isOk() ? std::move(result.value()) : nullptr;
+ return result.isOk() ? result.value() : nullptr;
}
class VibratorControllerWrapper {
@@ -192,6 +197,29 @@
return effect;
}
+static Aidl::VendorEffect vendorEffectFromJavaParcel(JNIEnv* env, jobject vendorData,
+ jlong strength, jfloat scale) {
+ PersistableBundle bundle;
+ if (AParcel* parcel = AParcel_fromJavaParcel(env, vendorData); parcel != nullptr) {
+ if (binder_status_t status = bundle.readFromParcel(parcel); status == STATUS_OK) {
+ AParcel_delete(parcel);
+ } else {
+ jniThrowExceptionFmt(env, "android/os/BadParcelableException",
+ "Failed to readFromParcel, status %d (%s)", status,
+ strerror(-status));
+ }
+ } else {
+ jniThrowExceptionFmt(env, "android/os/BadParcelableException",
+ "Failed to AParcel_fromJavaParcel, for nullptr");
+ }
+
+ Aidl::VendorEffect effect;
+ effect.vendorData = bundle;
+ effect.strength = static_cast<Aidl::EffectStrength>(strength);
+ effect.scale = static_cast<float>(scale);
+ return effect;
+}
+
static void destroyNativeWrapper(void* ptr) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper) {
@@ -289,6 +317,23 @@
return result.isOk() ? result.value().count() : (result.isUnsupported() ? 0 : -1);
}
+static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
+ jobject vendorData, jlong strength, jfloat scale,
+ jlong vibrationId) {
+ VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
+ if (wrapper == nullptr) {
+ ALOGE("vibratorPerformVendorEffect failed because native wrapper was not initialized");
+ return -1;
+ }
+ Aidl::VendorEffect effect = vendorEffectFromJavaParcel(env, vendorData, strength, scale);
+ auto callback = wrapper->createCallback(vibrationId);
+ auto performVendorEffectFn = [&effect, &callback](vibrator::HalWrapper* hal) {
+ return hal->performVendorEffect(effect, callback);
+ };
+ auto result = wrapper->halCall<void>(performVendorEffectFn, "performVendorEffect");
+ return result.isOk() ? std::numeric_limits<int64_t>::max() : (result.isUnsupported() ? 0 : -1);
+}
+
static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
jobjectArray composition, jlong vibrationId) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
@@ -466,6 +511,7 @@
{"off", "(J)V", (void*)vibratorOff},
{"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude},
{"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect},
+ {"performVendorEffect", "(JLandroid/os/Parcel;JFJ)J", (void*)vibratorPerformVendorEffect},
{"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;J)J",
(void*)vibratorPerformComposedEffect},
{"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJ)J",
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index a738acb..598e273 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -63,7 +63,7 @@
libs: [
"android.hardware.power-V1-java",
"android.hardware.tv.cec-V1.0-java",
- "android.hardware.vibrator-V2-java",
+ "android.hardware.vibrator-V3-java",
"android.hidl.manager-V1.0-java",
"android.test.mock",
"android.test.base",
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index b9e99dd..a888dad 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -94,7 +94,7 @@
libs: [
"android.hardware.power-V1-java",
"android.hardware.tv.cec-V1.0-java",
- "android.hardware.vibrator-V2-java",
+ "android.hardware.vibrator-V3-java",
"android.hidl.manager-V1.0-java",
"android.test.mock",
"android.test.base",
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index da21cd3..757bcd8 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -16,7 +16,7 @@
],
libs: [
- "android.hardware.vibrator-V2-java",
+ "android.hardware.vibrator-V3-java",
"android.test.mock",
"android.test.base",
"android.test.runner",
@@ -36,7 +36,6 @@
"platform-test-annotations",
"service-permission.stubs.system_server",
"services.core",
- "flag-junit",
],
jni_libs: ["libdexmakerjvmtiagent"],
platform_apis: true,
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
index 3013ed0..59d5577 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
@@ -25,6 +25,7 @@
import android.hardware.vibrator.IVibrator;
import android.os.CombinedVibration;
import android.os.Handler;
+import android.os.PersistableBundle;
import android.os.VibrationEffect;
import android.os.test.TestLooper;
import android.os.vibrator.PrebakedSegment;
@@ -32,6 +33,7 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -103,6 +105,17 @@
}
@Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void testVendorEffect_returnsOriginalSegment() {
+ PersistableBundle vendorData = new PersistableBundle();
+ vendorData.putInt("key", 1);
+ VibrationEffect effect = VibrationEffect.createVendorEffect(vendorData);
+
+ assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_ID, effect)).isEqualTo(effect);
+ assertThat(mAdapter.adaptToVibrator(PWLE_VIBRATOR_ID, effect)).isEqualTo(effect);
+ }
+
+ @Test
public void testStepAndRampSegments_withoutPwleCapability_convertsRampsToSteps() {
VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
// Step(amplitude, frequencyHz, duration)
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index b264435..9ebeaa8 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -37,6 +37,7 @@
import android.content.pm.PackageManagerInternal;
import android.os.ExternalVibrationScale;
import android.os.Handler;
+import android.os.PersistableBundle;
import android.os.PowerManagerInternal;
import android.os.UserHandle;
import android.os.VibrationAttributes;
@@ -232,6 +233,34 @@
}
@Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void scale_withVendorEffect_setsEffectStrengthBasedOnSettings() {
+ setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_LOW);
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+ PersistableBundle vendorData = new PersistableBundle();
+ vendorData.putString("key", "value");
+ VibrationEffect effect = VibrationEffect.createVendorEffect(vendorData);
+
+ VibrationEffect.VendorEffect scaled =
+ (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
+ assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
+
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ VIBRATION_INTENSITY_MEDIUM);
+ scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
+ assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+ scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
+ assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
+
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+ scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
+ // Vibration setting being bypassed will use default setting.
+ assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ }
+
+ @Test
public void scale_withOneShotAndWaveform_resolvesAmplitude() {
// No scale, default amplitude still resolved
setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_LOW);
@@ -365,6 +394,30 @@
assertTrue(scaled.getAmplitude() > 0.5);
}
+ @Test
+ @RequiresFlagsEnabled({
+ android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED,
+ android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS,
+ })
+ public void scale_adaptiveHapticsOnVendorEffect_setsLinearScaleParameter() {
+ setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
+
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+
+ PersistableBundle vendorData = new PersistableBundle();
+ vendorData.putInt("key", 1);
+ VibrationEffect effect = VibrationEffect.createVendorEffect(vendorData);
+
+ VibrationEffect.VendorEffect scaled =
+ (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_RINGTONE);
+ assertEquals(scaled.getLinearScale(), 0.5f);
+
+ mVibrationScaler.removeAdaptiveHapticsScale(USAGE_RINGTONE);
+
+ scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_RINGTONE);
+ assertEquals(scaled.getLinearScale(), 1.0f);
+ }
+
private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
@Vibrator.VibrationIntensity int intensity) {
when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index d7004e7..3bd56de 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -48,6 +48,7 @@
import android.os.CombinedVibration;
import android.os.Handler;
import android.os.IBinder;
+import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.Process;
import android.os.SystemClock;
@@ -560,8 +561,37 @@
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread =
new Thread(() -> mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(
- Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+ /* immediate= */ false));
+ cancellingThread.start();
+
+ waitForCompletion(/* timeout= */ 50);
+ cancellingThread.join();
+
+ verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE);
+ assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrate_singleVibratorVendorEffectCancel_cancelsVibrationImmediately()
+ throws Exception {
+ mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+ // Set long vendor effect duration to check it gets cancelled quickly.
+ mVibratorProviders.get(VIBRATOR_ID).setVendorEffectDuration(10 * TEST_TIMEOUT_MILLIS);
+
+ VibrationEffect effect = VibrationEffect.createVendorEffect(createTestVendorData());
+ long vibrationId = startThreadAndDispatcher(effect);
+
+ assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
+ TEST_TIMEOUT_MILLIS));
+ assertTrue(mThread.isRunningVibrationId(vibrationId));
+
+ // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
+ // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
+ Thread cancellingThread =
+ new Thread(() -> mVibrationConductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
/* immediate= */ false));
cancellingThread.start();
@@ -588,8 +618,7 @@
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread =
new Thread(() -> mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(
- Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
/* immediate= */ false));
cancellingThread.start();
@@ -654,6 +683,27 @@
}
@Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrate_singleVibratorVendorEffect_runsVibration() {
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+
+ VibrationEffect effect = VibrationEffect.createVendorEffect(createTestVendorData());
+ long vibrationId = startThreadAndDispatcher(effect);
+ waitForCompletion();
+
+ verify(mManagerHooks).noteVibratorOn(eq(UID),
+ eq(PerformVendorEffectVibratorStep.VENDOR_EFFECT_MAX_DURATION_MS));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+ verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+
+ assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibrationId))
+ .containsExactly(effect)
+ .inOrder();
+ }
+
+ @Test
public void vibrate_singleVibratorComposed_runsVibration() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
@@ -1437,16 +1487,48 @@
.combine();
long vibrationId = startThreadAndDispatcher(effect);
- assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(),
- TEST_TIMEOUT_MILLIS));
+ assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(), TEST_TIMEOUT_MILLIS));
assertTrue(mThread.isRunningVibrationId(vibrationId));
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread = new Thread(
() -> mVibrationConductor.notifyCancelled(
- new Vibration.EndInfo(
- Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ /* immediate= */ false));
+ cancellingThread.start();
+
+ waitForCompletion(/* timeout= */ 50);
+ cancellingThread.join();
+
+ verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+ assertFalse(mControllers.get(1).isVibrating());
+ assertFalse(mControllers.get(2).isVibrating());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrate_multipleVendorEffectCancel_cancelsVibrationImmediately() throws Exception {
+ mockVibrators(1, 2);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+ mVibratorProviders.get(1).setVendorEffectDuration(10 * TEST_TIMEOUT_MILLIS);
+ mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+ mVibratorProviders.get(2).setVendorEffectDuration(10 * TEST_TIMEOUT_MILLIS);
+
+ CombinedVibration effect = CombinedVibration.startParallel()
+ .addVibrator(1, VibrationEffect.createVendorEffect(createTestVendorData()))
+ .addVibrator(2, VibrationEffect.createVendorEffect(createTestVendorData()))
+ .combine();
+ long vibrationId = startThreadAndDispatcher(effect);
+
+ assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(), TEST_TIMEOUT_MILLIS));
+ assertTrue(mThread.isRunningVibrationId(vibrationId));
+
+ // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
+ // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
+ Thread cancellingThread = new Thread(
+ () -> mVibrationConductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
/* immediate= */ false));
cancellingThread.start();
@@ -1614,6 +1696,25 @@
}
@Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrate_vendorEffectWithRampDown_doesNotAddRampDown() {
+ when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
+ mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+
+ VibrationEffect effect = VibrationEffect.createVendorEffect(createTestVendorData());
+ long vibrationId = startThreadAndDispatcher(effect);
+ waitForCompletion();
+
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+ verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+
+ assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibrationId))
+ .containsExactly(effect)
+ .inOrder();
+ assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()).isEmpty();
+ }
+
+ @Test
public void vibrate_composedWithRampDown_doesNotAddRampDown() {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
@@ -1831,6 +1932,16 @@
return array;
}
+ private static PersistableBundle createTestVendorData() {
+ PersistableBundle vendorData = new PersistableBundle();
+ vendorData.putInt("id", 1);
+ vendorData.putDouble("scale", 0.5);
+ vendorData.putBoolean("loop", false);
+ vendorData.putLongArray("amplitudes", new long[] { 0, 255, 128 });
+ vendorData.putString("label", "vibration");
+ return vendorData;
+ }
+
private VibrationEffectSegment expectedOneShot(long millis) {
return new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
/* frequencyHz= */ 0, (int) millis);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 5ae5677b9b5..1f4a469 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.vibrator;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -67,6 +69,7 @@
import android.os.IExternalVibrationController;
import android.os.IVibratorStateListener;
import android.os.Looper;
+import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
@@ -1573,6 +1576,50 @@
}
@Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrate_vendorEffectsWithoutPermission_doesNotVibrate() throws Exception {
+ // Deny permission to vibrate with vendor effects
+ denyPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS);
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_TICK);
+ VibratorManagerService service = createSystemReadyService();
+
+ PersistableBundle vendorData = new PersistableBundle();
+ vendorData.putString("key", "value");
+ VibrationEffect vendorEffect = VibrationEffect.createVendorEffect(vendorData);
+ VibrationEffect tickEffect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK);
+
+ vibrateAndWaitUntilFinished(service, vendorEffect, RINGTONE_ATTRS);
+ vibrateAndWaitUntilFinished(service, tickEffect, RINGTONE_ATTRS);
+
+ // No vendor effect played, but predefined TICK plays successfully.
+ assertThat(fakeVibrator.getAllVendorEffects()).isEmpty();
+ assertThat(fakeVibrator.getAllEffectSegments()).hasSize(1);
+ assertThat(fakeVibrator.getAllEffectSegments().get(0)).isInstanceOf(PrebakedSegment.class);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrate_vendorEffectsWithPermission_successful() throws Exception {
+ // Deny permission to vibrate with vendor effects
+ grantPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS);
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+ VibratorManagerService service = createSystemReadyService();
+
+ PersistableBundle vendorData = new PersistableBundle();
+ vendorData.putString("key", "value");
+ VibrationEffect vendorEffect = VibrationEffect.createVendorEffect(vendorData);
+
+ vibrateAndWaitUntilFinished(service, vendorEffect, RINGTONE_ATTRS);
+
+ assertThat(fakeVibrator.getAllVendorEffects()).containsExactly(vendorEffect);
+ }
+
+ @Test
public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
int defaultNotificationIntensity =
mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION);
@@ -1714,6 +1761,39 @@
}
@Test
+ @RequiresFlagsEnabled({
+ android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED,
+ android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS,
+ })
+ public void vibrate_withIntensitySettingsAndAdaptiveHaptics_appliesSettingsToVendorEffects()
+ throws Exception {
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_LOW);
+
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+ VibratorManagerService service = createSystemReadyService();
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.setVibrationParams(
+ VibrationParamGenerator.generateVibrationParams(vibrationScales),
+ mFakeVibratorController);
+
+ PersistableBundle vendorData = new PersistableBundle();
+ vendorData.putString("key", "value");
+ VibrationEffect vendorEffect = VibrationEffect.createVendorEffect(vendorData);
+ vibrateAndWaitUntilFinished(service, vendorEffect, NOTIFICATION_ATTRS);
+
+ assertThat(fakeVibrator.getAllVendorEffects()).hasSize(1);
+ VibrationEffect.VendorEffect scaled = fakeVibrator.getAllVendorEffects().get(0);
+ assertThat(scaled.getEffectStrength()).isEqualTo(VibrationEffect.EFFECT_STRENGTH_STRONG);
+ assertThat(scaled.getLinearScale()).isEqualTo(0.4f);
+ }
+
+ @Test
public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
mockVibrators(1, 2);
VibratorManagerService service = createSystemReadyService();
@@ -2729,7 +2809,9 @@
CombinedVibration effect, VibrationAttributes attrs) {
HalVibration vib = service.vibrateWithPermissionCheck(UID, deviceId, PACKAGE_NAME, effect,
attrs, "some reason", service);
- mPendingVibrations.add(vib);
+ if (vib != null) {
+ mPendingVibrations.add(vib);
+ }
return vib;
}
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 2ddb47b..96c3e97 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -17,8 +17,11 @@
package com.android.server.vibrator;
import android.annotation.Nullable;
+import android.hardware.vibrator.IVibrator;
import android.os.Handler;
import android.os.Looper;
+import android.os.Parcel;
+import android.os.PersistableBundle;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.PrebakedSegment;
@@ -45,6 +48,7 @@
private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
private final Map<Long, List<VibrationEffectSegment>> mEffectSegments = new TreeMap<>();
+ private final Map<Long, List<VibrationEffect.VendorEffect>> mVendorEffects = new TreeMap<>();
private final Map<Long, List<Integer>> mBraking = new HashMap<>();
private final List<Float> mAmplitudes = new ArrayList<>();
private final List<Boolean> mExternalControlStates = new ArrayList<>();
@@ -69,11 +73,16 @@
private float mFrequencyResolution = Float.NaN;
private float mQFactor = Float.NaN;
private float[] mMaxAmplitudes;
+ private long mVendorEffectDuration = EFFECT_DURATION;
void recordEffectSegment(long vibrationId, VibrationEffectSegment segment) {
mEffectSegments.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(segment);
}
+ void recordVendorEffect(long vibrationId, VibrationEffect.VendorEffect vendorEffect) {
+ mVendorEffects.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(vendorEffect);
+ }
+
void recordBraking(long vibrationId, int braking) {
mBraking.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(braking);
}
@@ -130,6 +139,21 @@
}
@Override
+ public long performVendorEffect(Parcel vendorData, long strength, float scale,
+ long vibrationId) {
+ if ((mCapabilities & IVibrator.CAP_PERFORM_VENDOR_EFFECTS) == 0) {
+ return 0;
+ }
+ PersistableBundle bundle = PersistableBundle.CREATOR.createFromParcel(vendorData);
+ recordVendorEffect(vibrationId,
+ new VibrationEffect.VendorEffect(bundle, (int) strength, scale));
+ applyLatency(mOnLatency);
+ scheduleListener(mVendorEffectDuration, vibrationId);
+ // HAL has unknown duration for vendor effects.
+ return Long.MAX_VALUE;
+ }
+
+ @Override
public long compose(PrimitiveSegment[] primitives, long vibrationId) {
if (mSupportedPrimitives == null) {
return 0;
@@ -328,6 +352,11 @@
mMaxAmplitudes = maxAmplitudes;
}
+ /** Set the duration of vendor effects in fake vibrator hardware. */
+ public void setVendorEffectDuration(long durationMs) {
+ mVendorEffectDuration = durationMs;
+ }
+
/**
* Return the amplitudes set by this controller, including zeroes for each time the vibrator was
* turned off.
@@ -366,6 +395,29 @@
}
return result;
}
+
+ /** Return list of {@link VibrationEffect.VendorEffect} played by this controller, in order. */
+ public List<VibrationEffect.VendorEffect> getVendorEffects(long vibrationId) {
+ if (mVendorEffects.containsKey(vibrationId)) {
+ return new ArrayList<>(mVendorEffects.get(vibrationId));
+ } else {
+ return new ArrayList<>();
+ }
+ }
+
+ /**
+ * Returns a list of all vibrations' effect segments, for external-use where vibration IDs
+ * aren't exposed.
+ */
+ public List<VibrationEffect.VendorEffect> getAllVendorEffects() {
+ // Returns segments in order of vibrationId, which increases over time. TreeMap gives order.
+ ArrayList<VibrationEffect.VendorEffect> result = new ArrayList<>();
+ for (List<VibrationEffect.VendorEffect> subList : mVendorEffects.values()) {
+ result.addAll(subList);
+ }
+ return result;
+ }
+
/** Return list of states set for external control to the fake vibrator hardware. */
public List<Boolean> getExternalControlStates() {
return mExternalControlStates;
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 867f01f..11f7560 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -190,6 +193,27 @@
doReturn(embedded).when(mActivityStack.top()).isEmbedded();
}
+ void setTopActivityInMultiWindowMode(boolean multiWindowMode) {
+ doReturn(multiWindowMode).when(mActivityStack.top()).inMultiWindowMode();
+ if (multiWindowMode) {
+ doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivityStack.top()).getWindowingMode();
+ }
+ }
+
+ void setTopActivityInPinnedWindowingMode(boolean pinnedWindowingMode) {
+ doReturn(pinnedWindowingMode).when(mActivityStack.top()).inPinnedWindowingMode();
+ if (pinnedWindowingMode) {
+ doReturn(WINDOWING_MODE_PINNED).when(mActivityStack.top()).getWindowingMode();
+ }
+ }
+
+ void setTopActivityInFreeformWindowingMode(boolean freeformWindowingMode) {
+ doReturn(freeformWindowingMode).when(mActivityStack.top()).inFreeformWindowingMode();
+ if (freeformWindowingMode) {
+ doReturn(WINDOWING_MODE_FREEFORM).when(mActivityStack.top()).getWindowingMode();
+ }
+ }
+
void destroyTopActivity() {
mActivityStack.top().removeImmediately();
}
@@ -401,9 +425,11 @@
private void pushActivity(@NonNull ActivityRecord activity) {
mActivityStack.push(activity);
spyOn(activity);
+ // TODO (b/351763164): Use these spyOn calls only when necessary.
spyOn(activity.mAppCompatController.getTransparentPolicy());
spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
+ spyOn(activity.mAppCompatController.getAppCompatFocusOverrides());
spyOn(activity.mLetterboxUiController);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
index 0a1b16b..00a8771 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
@@ -66,4 +66,8 @@
doReturn(enabled).when(mAppCompatConfiguration)
.isCameraCompatSplitScreenAspectRatioEnabled();
}
+
+ void enableCompatFakeFocus(boolean enabled) {
+ doReturn(enabled).when(mAppCompatConfiguration).isCompatFakeFocusEnabled();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java
new file mode 100644
index 0000000..27c5e4e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
+import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.testng.Assert;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link AppCompatFocusOverrides}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatFocusOverridesTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatFocusOverridesTest extends WindowTestsBase {
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_overrideEnabled_inMultiWindow_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setTopActivityInMultiWindowMode(/* multiWindowMode */ true);
+ });
+
+ robot.checkShouldSendFakeFocusOnTopActivity(/* expected */ true);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_overrideEnabled_noMultiWindowMode_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setTopActivityInMultiWindowMode(/* multiWindowMode */ false);
+ });
+
+ robot.checkShouldSendFakeFocusOnTopActivity(/* expected */ false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_overrideEnabled_pinnedWindowMode_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setTopActivityInPinnedWindowingMode(/* multiWindowMode */ true);
+ });
+
+ robot.checkShouldSendFakeFocusOnTopActivity(/* expected */ false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_overrideEnabled_freeformMode_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setTopActivityInFreeformWindowingMode(/* freeformWindowingMode */ true);
+ });
+
+ robot.checkShouldSendFakeFocusOnTopActivity(/* expected */ false);
+ });
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_overrideDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setTopActivityInMultiWindowMode(/* multiWindowMode */ true);
+ });
+ robot.checkShouldSendFakeFocusOnTopActivity(/* expected */ false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testIsCompatFakeFocusEnabled_propertyDisabledAndOverrideOn_fakeFocusDisabled() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.prop().disable(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setTopActivityInMultiWindowMode(/* multiWindowMode */ true);
+ });
+ robot.checkShouldSendFakeFocusOnTopActivity(/* expected */ false);
+ });
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testIsCompatFakeFocusEnabled_propertyEnabled_noOverride_fakeFocusEnabled() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.prop().enable(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setTopActivityInMultiWindowMode(/* multiWindowMode */ true);
+ });
+ robot.checkShouldSendFakeFocusOnTopActivity(/* expected */ true);
+ });
+ }
+
+ @Test
+ public void testIsCompatFakeFocusEnabled_propertyDisabled_fakeFocusDisabled() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.prop().disable(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setTopActivityInMultiWindowMode(/* multiWindowMode */ true);
+ });
+ robot.checkShouldSendFakeFocusOnTopActivity(/* expected */ false);
+ });
+ }
+
+ @Test
+ public void testIsCompatFakeFocusEnabled_propertyEnabled_fakeFocusEnabled() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.prop().enable(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setTopActivityInMultiWindowMode(/* multiWindowMode */ true);
+ });
+ robot.checkShouldSendFakeFocusOnTopActivity(/* expected */ true);
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<FocusOverridesRobotTest> consumer) {
+ spyOn(mWm.mAppCompatConfiguration);
+ final FocusOverridesRobotTest robot = new FocusOverridesRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class FocusOverridesRobotTest extends AppCompatRobotBase {
+
+ FocusOverridesRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ }
+
+ void checkShouldSendFakeFocusOnTopActivity(boolean expected) {
+ Assert.assertEquals(activity().top().mAppCompatController.getAppCompatFocusOverrides()
+ .shouldSendFakeFocus(), expected);
+ }
+ }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatSizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatSizeCompatTests.java
new file mode 100644
index 0000000..439c633
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatSizeCompatTests.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
+import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.MediumTest;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+/**
+ * Tests for App Compat specific code about sizes.
+ *
+ * Build/Install/Run:
+ * atest WmTests:AppCompatSizeCompatTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatSizeCompatTests extends WindowTestsBase {
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_compatFakeFocusEnabledUnsetProp() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.activity().createActivityWithComponent();
+
+ robot.putTopActivityInMultiWindowMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ true);
+
+ robot.putTopActivityInPinnedWindowingMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+
+ robot.putTopActivityInFreeformWindowingMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_compatFakeFocusEnabledTrueProp() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.prop().enable(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS);
+ robot.activity().createActivityWithComponent();
+
+ robot.putTopActivityInMultiWindowMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ true);
+
+ robot.putTopActivityInPinnedWindowingMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+
+ robot.putTopActivityInFreeformWindowingMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_compatFakeFocusEnabledFalseProp() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.prop().disable(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS);
+ robot.activity().createActivityWithComponent();
+
+ robot.putTopActivityInMultiWindowMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+
+ robot.putTopActivityInPinnedWindowingMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+
+ robot.putTopActivityInFreeformWindowingMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+ });
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_compatFakeFocusDisabledUnsetProp() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.activity().createActivityWithComponent();
+
+ robot.putTopActivityInMultiWindowMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+
+ robot.putTopActivityInPinnedWindowingMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+
+ robot.putTopActivityInFreeformWindowingMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+ });
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_compatFakeFocusDisabledTrueProp() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.prop().enable(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS);
+ robot.activity().createActivityWithComponent();
+
+ robot.putTopActivityInMultiWindowMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ true);
+
+ robot.putTopActivityInPinnedWindowingMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+
+ robot.putTopActivityInFreeformWindowingMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+ });
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_compatFakeFocusDisabledFalseProp() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ true);
+ robot.prop().disable(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS);
+ robot.activity().createActivityWithComponent();
+
+ robot.putTopActivityInMultiWindowMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+
+ robot.putTopActivityInPinnedWindowingMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+
+ robot.putTopActivityInFreeformWindowingMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_compatFakeFocusEnabledFeatureDisabled() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCompatFakeFocus(/* enabled */ false);
+ robot.activity().createActivityWithComponent();
+
+ robot.putTopActivityInMultiWindowMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+
+ robot.putTopActivityInPinnedWindowingMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+
+ robot.putTopActivityInFreeformWindowingMode();
+ robot.checkShouldSendCompatFakeFocus(/* expected */ false);
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<SizeCompatRobotTest> consumer) {
+ spyOn(mWm.mAppCompatConfiguration);
+ final SizeCompatRobotTest robot = new SizeCompatRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class SizeCompatRobotTest extends AppCompatRobotBase {
+
+ SizeCompatRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ }
+
+ void checkShouldSendCompatFakeFocus(boolean expected) {
+ Assert.assertEquals(expected, activity().top().shouldSendCompatFakeFocus());
+ }
+
+ void putTopActivityInMultiWindowMode() {
+ applyOnActivity((a) -> {
+ a.setTopActivityInMultiWindowMode(true);
+ a.setTopActivityInFreeformWindowingMode(false);
+ a.setTopActivityInPinnedWindowingMode(false);
+ });
+ }
+
+ void putTopActivityInPinnedWindowingMode() {
+ applyOnActivity((a) -> {
+ a.setTopActivityInMultiWindowMode(false);
+ a.setTopActivityInPinnedWindowingMode(true);
+ a.setTopActivityInFreeformWindowingMode(false);
+ });
+ }
+
+ void putTopActivityInFreeformWindowingMode() {
+ applyOnActivity((a) -> {
+ a.setTopActivityInMultiWindowMode(false);
+ a.setTopActivityInPinnedWindowingMode(false);
+ a.setTopActivityInFreeformWindowingMode(true);
+ });
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 08f1dff..771e290 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -32,11 +32,13 @@
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import com.android.server.testutils.StubTransaction;
import com.android.server.wm.utils.MockAnimationAdapter;
+import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -142,11 +144,12 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_USE_TASKS_DIM_ONLY)
public void testUpdateDimsAppliesCrop() {
mHost.addChild(mChild, 0);
mDimmer.adjustAppearance(mChild, 1, 1);
- mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.adjustPosition(mChild, mChild, -1);
int width = 100;
int height = 300;
@@ -163,7 +166,7 @@
final int blur = 50;
mHost.addChild(mChild, 0);
mDimmer.adjustAppearance(mChild, alpha, blur);
- mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.adjustPosition(mChild, mChild, -1);
SurfaceControl dimLayer = mDimmer.getDimLayer();
assertNotNull("Dimmer should have created a surface", dimLayer);
@@ -184,12 +187,12 @@
final int blur = 50;
// Dim once
mDimmer.adjustAppearance(mChild, alpha, blur);
- mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.adjustPosition(mChild, mChild, -1);
SurfaceControl dimLayer = mDimmer.getDimLayer();
mDimmer.updateDims(mTransaction);
// Reset, and don't dim
mDimmer.resetDimStates();
- mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.adjustPosition(mChild, mChild, -1);
mDimmer.updateDims(mTransaction);
verify(mTransaction).show(dimLayer);
verify(mTransaction).remove(dimLayer);
@@ -203,24 +206,25 @@
final int blur = 20;
// Dim once
mDimmer.adjustAppearance(mChild, alpha, blur);
- mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.adjustPosition(mChild, mChild, -1);
SurfaceControl dimLayer = mDimmer.getDimLayer();
mDimmer.updateDims(mTransaction);
// Reset and dim again
mDimmer.resetDimStates();
mDimmer.adjustAppearance(mChild, alpha, blur);
- mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.adjustPosition(mChild, mChild, -1);
mDimmer.updateDims(mTransaction);
verify(mTransaction).show(dimLayer);
verify(mTransaction, never()).remove(dimLayer);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_USE_TASKS_DIM_ONLY)
public void testDimUpdateWhileDimming() {
mHost.addChild(mChild, 0);
final float alpha = 0.8f;
mDimmer.adjustAppearance(mChild, alpha, 20);
- mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.adjustPosition(mChild, mChild, -1);
final Rect bounds = mDimmer.getDimBounds();
SurfaceControl dimLayer = mDimmer.getDimLayer();
@@ -240,7 +244,7 @@
public void testRemoveDimImmediately() {
mHost.addChild(mChild, 0);
mDimmer.adjustAppearance(mChild, 1, 2);
- mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.adjustPosition(mChild, mChild, -1);
SurfaceControl dimLayer = mDimmer.getDimLayer();
mDimmer.updateDims(mTransaction);
verify(mTransaction, times(1)).show(dimLayer);
@@ -265,18 +269,18 @@
mDimmer.resetDimStates();
mDimmer.adjustAppearance(mChild, 0.1f, 0);
- mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.adjustPosition(mChild, mChild, -1);
SurfaceControl dimLayer = mDimmer.getDimLayer();
mDimmer.updateDims(mTransaction);
mDimmer.resetDimStates();
mDimmer.adjustAppearance(mChild, 0.2f, 0);
- mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.adjustPosition(mChild, mChild, -1);
mDimmer.updateDims(mTransaction);
mDimmer.resetDimStates();
mDimmer.adjustAppearance(mChild, 0.3f, 0);
- mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.adjustPosition(mChild, mChild, -1);
mDimmer.updateDims(mTransaction);
verify(mTransaction).setAlpha(dimLayer, 0.2f);
@@ -296,18 +300,18 @@
mDimmer.resetDimStates();
mDimmer.adjustAppearance(mChild, 0.2f, 0);
- mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.adjustPosition(mChild, mChild, -1);
SurfaceControl dimLayer = mDimmer.getDimLayer();
mDimmer.updateDims(mTransaction);
mDimmer.resetDimStates();
mDimmer.adjustAppearance(mChild, 0.1f, 0);
- mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.adjustPosition(mChild, mChild, -1);
mDimmer.updateDims(mTransaction);
mDimmer.resetDimStates();
mDimmer.adjustAppearance(mChild, 0f, 0);
- mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.adjustPosition(mChild, mChild, -1);
mDimmer.updateDims(mTransaction);
mDimmer.resetDimStates();
@@ -326,13 +330,13 @@
mHost.addChild(second, 1);
mDimmer.adjustAppearance(first, 0.5f, 0);
- mDimmer.adjustRelativeLayer(first, -1);
+ mDimmer.adjustPosition(mChild, first, -1);
SurfaceControl dimLayer = mDimmer.getDimLayer();
mDimmer.updateDims(mTransaction);
mDimmer.resetDimStates();
mDimmer.adjustAppearance(second, 0.9f, 0);
- mDimmer.adjustRelativeLayer(second, -1);
+ mDimmer.adjustPosition(mChild, second, -1);
mDimmer.updateDims(mTransaction);
verify(sTestAnimation, times(2)).startAnimation(
@@ -354,10 +358,10 @@
mHost.addChild(second, 1);
mDimmer.adjustAppearance(first, 0.5f, 0);
- mDimmer.adjustRelativeLayer(first, -1);
+ mDimmer.adjustPosition(mChild, first, -1);
SurfaceControl dimLayer = mDimmer.getDimLayer();
mDimmer.adjustAppearance(second, 0.9f, 0);
- mDimmer.adjustRelativeLayer(second, -1);
+ mDimmer.adjustPosition(mChild, second, -1);
mDimmer.updateDims(mTransaction);
verify(sTestAnimation, times(1)).startAnimation(
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 44c7057b..a0a2904 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -18,14 +18,12 @@
import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
-import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
-import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
@@ -362,72 +360,6 @@
}
@Test
- @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
- public void testShouldSendFakeFocus_overrideEnabled_returnsTrue() {
- doReturn(true).when(mAppCompatConfiguration).isCompatFakeFocusEnabled();
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mController.shouldSendFakeFocus());
- }
-
- @Test
- @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
- public void testShouldSendFakeFocus_overrideDisabled_returnsFalse() {
- doReturn(true).when(mAppCompatConfiguration).isCompatFakeFocusEnabled();
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldSendFakeFocus());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
- public void testIsCompatFakeFocusEnabled_propertyDisabledAndOverrideEnabled_fakeFocusDisabled()
- throws Exception {
- doReturn(true).when(mAppCompatConfiguration).isCompatFakeFocusEnabled();
- mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ false);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldSendFakeFocus());
- }
-
- @Test
- @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
- public void testIsCompatFakeFocusEnabled_propertyEnabled_noOverride_fakeFocusEnabled()
- throws Exception {
- doReturn(true).when(mAppCompatConfiguration).isCompatFakeFocusEnabled();
- mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ true);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mController.shouldSendFakeFocus());
- }
-
- @Test
- public void testIsCompatFakeFocusEnabled_propertyDisabled_fakeFocusDisabled()
- throws Exception {
- doReturn(true).when(mAppCompatConfiguration).isCompatFakeFocusEnabled();
- mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ false);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldSendFakeFocus());
- }
-
- @Test
- public void testIsCompatFakeFocusEnabled_propertyEnabled_fakeFocusEnabled()
- throws Exception {
- doReturn(true).when(mAppCompatConfiguration).isCompatFakeFocusEnabled();
- mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ true);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mController.shouldSendFakeFocus());
- }
-
- @Test
@EnableCompatChanges({FORCE_RESIZE_APP})
public void testshouldOverrideForceResizeApp_overrideEnabled_returnsTrue() {
mController = new LetterboxUiController(mWm, mActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 8981f71..72747c9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -19,7 +19,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
@@ -4809,52 +4808,6 @@
assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
}
- @Test
- public void testShouldSendFakeFocus_compatFakeFocusEnabled() {
- final ActivityRecord activity = new ActivityBuilder(mAtm)
- .setCreateTask(true)
- .setOnTop(true)
- // Set the component to be that of the test class in order to enable compat changes
- .setComponent(ComponentName.createRelative(mContext,
- com.android.server.wm.SizeCompatTests.class.getName()))
- .build();
- final Task task = activity.getTask();
- spyOn(activity.mLetterboxUiController);
- doReturn(true).when(activity.mLetterboxUiController).shouldSendFakeFocus();
-
- task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- assertTrue(activity.shouldSendCompatFakeFocus());
-
- task.setWindowingMode(WINDOWING_MODE_PINNED);
- assertFalse(activity.shouldSendCompatFakeFocus());
-
- task.setWindowingMode(WINDOWING_MODE_FREEFORM);
- assertFalse(activity.shouldSendCompatFakeFocus());
- }
-
- @Test
- public void testShouldSendFakeFocus_compatFakeFocusDisabled() {
- final ActivityRecord activity = new ActivityBuilder(mAtm)
- .setCreateTask(true)
- .setOnTop(true)
- // Set the component to be that of the test class in order to enable compat changes
- .setComponent(ComponentName.createRelative(mContext,
- com.android.server.wm.SizeCompatTests.class.getName()))
- .build();
- final Task task = activity.getTask();
- spyOn(activity.mLetterboxUiController);
- doReturn(false).when(activity.mLetterboxUiController).shouldSendFakeFocus();
-
- task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- assertFalse(activity.shouldSendCompatFakeFocus());
-
- task.setWindowingMode(WINDOWING_MODE_PINNED);
- assertFalse(activity.shouldSendCompatFakeFocus());
-
- task.setWindowingMode(WINDOWING_MODE_FREEFORM);
- assertFalse(activity.shouldSendCompatFakeFocus());
- }
-
private void setUpAllowThinLetterboxed(boolean thinLetterboxAllowed) {
spyOn(mActivity.mLetterboxUiController);
doReturn(thinLetterboxAllowed).when(mActivity.mLetterboxUiController)
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
index eda78cb..381e9e4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
@@ -20,7 +20,7 @@
import static com.android.server.wm.utils.DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_OFF;
import static com.android.server.wm.utils.DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_ON;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE;
-import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
+import static com.android.window.flags.Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS;
import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION;
import static com.google.common.truth.Truth.assertThat;
@@ -188,145 +188,145 @@
@Test
@EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY})
+ FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
public void isEnabled_dwFlagOn_overrideUnset_featureFlagOn_returnsTrue() {
setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
// For unset overrides, follow flag
- assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue();
+ assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
}
@Test
@EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
public void isEnabled_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() {
setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
// For unset overrides, follow flag
- assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse();
+ assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
}
@Test
@EnableFlags({
FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+ FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
})
public void isEnabled_dwFlagOn_overrideOn_featureFlagOn_returnsTrue() {
setOverride(OVERRIDE_ON.getSetting());
// When toggle override matches its default state (dw flag), don't override flags
- assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue();
+ assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
}
@Test
@EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
public void isEnabled_dwFlagOn_overrideOn_featureFlagOff_returnsFalse() {
setOverride(OVERRIDE_ON.getSetting());
// When toggle override matches its default state (dw flag), don't override flags
- assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse();
+ assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
}
@Test
@EnableFlags({
FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+ FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
})
public void isEnabled_dwFlagOn_overrideOff_featureFlagOn_returnsFalse() {
setOverride(OVERRIDE_OFF.getSetting());
// Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse();
+ assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
}
@Test
@EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
- @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
public void isEnabled_dwFlagOn_overrideOff_featureFlagOff_returnsFalse() {
setOverride(OVERRIDE_OFF.getSetting());
// Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse();
+ assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
}
@Test
@EnableFlags({
FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
- FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+ FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
})
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void isEnabled_dwFlagOff_overrideUnset_featureFlagOn_returnsTrue() {
setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
// For unset overrides, follow flag
- assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue();
+ assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
}
@Test
@EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@DisableFlags({
FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+ FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
})
public void isEnabled_dwFlagOff_overrideUnset_featureFlagOff_returnsFalse() {
setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting());
// For unset overrides, follow flag
- assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse();
+ assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
}
@Test
@EnableFlags({
FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
- FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+ FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
})
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void isEnabled_dwFlagOff_overrideOn_featureFlagOn_returnsTrue() {
setOverride(OVERRIDE_ON.getSetting());
// Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue();
+ assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
}
@Test
@EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@DisableFlags({
FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+ FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
})
public void isEnabled_dwFlagOff_overrideOn_featureFlagOff_returnTrue() {
setOverride(OVERRIDE_ON.getSetting());
// Follow override if they exist, and is not equal to default toggle state (dw flag)
- assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue();
+ assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
}
@Test
@EnableFlags({
FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
- FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+ FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
})
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void isEnabled_dwFlagOff_overrideOff_featureFlagOn_returnsTrue() {
setOverride(OVERRIDE_OFF.getSetting());
// When toggle override matches its default state (dw flag), don't override flags
- assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue();
+ assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
}
@Test
@EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@DisableFlags({
FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
- FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+ FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
})
public void isEnabled_dwFlagOff_overrideOff_featureFlagOff_returnsFalse() {
setOverride(OVERRIDE_OFF.getSetting());
// When toggle override matches its default state (dw flag), don't override flags
- assertThat(DesktopModeFlagsUtil.WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse();
+ assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
}
private void setOverride(Integer setting) {