Merge "[atoms.proto] Refactored apps shared enums files" into main
diff --git a/Android.bp b/Android.bp
index eabd9c7..6fa6650 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",
@@ -636,7 +636,6 @@
"core/java/com/android/internal/util/AsyncService.java",
"core/java/com/android/internal/util/Protocol.java",
"telephony/java/android/telephony/Annotation.java",
- ":net-utils-framework-wifi-common-srcs",
],
libs: [
"framework-annotations-lib",
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index ee03e4b..857154f 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -116,7 +116,6 @@
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.ThreadLocalWorkSource;
import android.os.Trace;
import android.os.UserHandle;
@@ -179,9 +178,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
-import java.time.Instant;
-import java.time.zone.ZoneOffsetTransition;
-import java.time.zone.ZoneRules;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -193,7 +189,6 @@
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.concurrent.ThreadLocalRandom;
-import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/**
@@ -233,13 +228,6 @@
private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY;
- // System properties read on some device configurations to initialize time properly and
- // perform DST transitions at the bootloader level.
- private static final String TIMEOFFSET_PROPERTY = "persist.sys.time.offset";
- private static final String DST_TRANSITION_PROPERTY = "persist.sys.time.dst_transition";
- private static final String DST_OFFSET_PROPERTY = "persist.sys.time.dst_offset";
-
-
private final Intent mBackgroundIntent
= new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
@@ -2127,22 +2115,6 @@
// "GMT" if the ID is unrecognized). The parameter ID is used here rather than
// newZone.getId(). It will be rejected if it is invalid.
timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence, logInfo);
-
- final int gmtOffset = newZone.getOffset(mInjector.getCurrentTimeMillis());
- SystemProperties.set(TIMEOFFSET_PROPERTY, String.valueOf(gmtOffset));
-
- final ZoneRules rules = newZone.toZoneId().getRules();
- final ZoneOffsetTransition transition = rules.nextTransition(Instant.now());
- if (null != transition) {
- // Get the offset between the time after the DST transition and before.
- final long transitionOffset = TimeUnit.SECONDS.toMillis((
- transition.getOffsetAfter().getTotalSeconds()
- - transition.getOffsetBefore().getTotalSeconds()));
- // Time when the next DST transition is programmed.
- final long nextTransition = TimeUnit.SECONDS.toMillis(transition.toEpochSecond());
- SystemProperties.set(DST_TRANSITION_PROPERTY, String.valueOf(nextTransition));
- SystemProperties.set(DST_OFFSET_PROPERTY, String.valueOf(transitionOffset));
- }
}
// Clear the default time zone in the system server process. This forces the next call
diff --git a/core/api/current.txt b/core/api/current.txt
index 69c409b..d610f4c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -54862,8 +54862,6 @@
method @Deprecated public void addAction(int);
method public void addChild(android.view.View);
method public void addChild(android.view.View, int);
- method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View);
- method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View, int);
method public boolean canOpenPopup();
method public int describeContents();
method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String);
@@ -54892,7 +54890,6 @@
method public int getInputType();
method public android.view.accessibility.AccessibilityNodeInfo getLabelFor();
method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
- method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") @NonNull public java.util.List<android.view.accessibility.AccessibilityNodeInfo> getLabeledByList();
method public int getLiveRegion();
method public int getMaxTextLength();
method @NonNull public java.time.Duration getMinDurationBetweenContentChanges();
@@ -54953,8 +54950,6 @@
method public boolean removeAction(android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction);
method public boolean removeChild(android.view.View);
method public boolean removeChild(android.view.View, int);
- method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View);
- method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View, int);
method public void setAccessibilityDataSensitive(boolean);
method public void setAccessibilityFocused(boolean);
method public void setAvailableExtraData(java.util.List<java.lang.String>);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 36a335e..7674246 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";
@@ -6717,6 +6718,14 @@
field public static final int STATUS_OK = 0; // 0x0
}
+ @FlaggedApi("android.media.soundtrigger.sound_trigger_generic_model_api") public static final class SoundTrigger.GenericSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {
+ ctor public SoundTrigger.GenericSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], int);
+ ctor public SoundTrigger.GenericSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[]);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.GenericSoundModel> CREATOR;
+ }
+
public static final class SoundTrigger.Keyphrase implements android.os.Parcelable {
ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]);
method public int describeContents();
@@ -11354,6 +11363,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/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 75f2c4b..75aab7d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3818,8 +3818,7 @@
final ArrayList<ResultInfo> list = new ArrayList<>();
list.add(new ResultInfo(id, requestCode, resultCode, data, activityToken));
final ClientTransaction clientTransaction = new ClientTransaction(mAppThread);
- final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
- activityToken, list);
+ final ActivityResultItem activityResultItem = new ActivityResultItem(activityToken, list);
clientTransaction.addTransactionItem(activityResultItem);
try {
mAppThread.scheduleTransaction(clientTransaction);
@@ -6239,7 +6238,7 @@
r.createdConfig != null
? r.createdConfig : mConfigurationController.getConfiguration(),
r.overrideConfig);
- final ActivityRelaunchItem activityRelaunchItem = ActivityRelaunchItem.obtain(
+ final ActivityRelaunchItem activityRelaunchItem = new ActivityRelaunchItem(
r.token, null /* pendingResults */, null /* pendingIntents */,
0 /* configChanges */, mergedConfiguration, r.mPreserveWindow,
r.getActivityWindowInfo());
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index 45bf235..cecf701 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -18,6 +18,8 @@
import static android.app.ActivityThread.DEBUG_ORDER;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
@@ -39,25 +41,50 @@
/**
* Activity relaunch callback.
+ *
* @hide
*/
public class ActivityRelaunchItem extends ActivityTransactionItem {
private static final String TAG = "ActivityRelaunchItem";
- private List<ResultInfo> mPendingResults;
- private List<ReferrerIntent> mPendingNewIntents;
- private int mConfigChanges;
- private MergedConfiguration mConfig;
- private boolean mPreserveWindow;
- private ActivityWindowInfo mActivityWindowInfo;
+ @Nullable
+ private final List<ResultInfo> mPendingResults;
+
+ @Nullable
+ private final List<ReferrerIntent> mPendingNewIntents;
+
+ @NonNull
+ private final MergedConfiguration mConfig;
+
+ @NonNull
+ private final ActivityWindowInfo mActivityWindowInfo;
+
+ private final int mConfigChanges;
+ private final boolean mPreserveWindow;
/**
* A record that was properly configured for relaunch. Execution will be cancelled if not
* initialized after {@link #preExecute(ClientTransactionHandler)}.
*/
+ @Nullable
private ActivityClientRecord mActivityClientRecord;
+ public ActivityRelaunchItem(@NonNull IBinder activityToken,
+ @Nullable List<ResultInfo> pendingResults,
+ @Nullable List<ReferrerIntent> pendingNewIntents, int configChanges,
+ @NonNull MergedConfiguration config, boolean preserveWindow,
+ @NonNull ActivityWindowInfo activityWindowInfo) {
+ super(activityToken);
+ mPendingResults = pendingResults != null ? new ArrayList<>(pendingResults) : null;
+ mPendingNewIntents =
+ pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null;
+ mConfig = new MergedConfiguration(config);
+ mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo);
+ mConfigChanges = configChanges;
+ mPreserveWindow = preserveWindow;
+ }
+
@Override
public void preExecute(@NonNull ClientTransactionHandler client) {
// The local config is already scaled so only apply if this item is from server side.
@@ -87,70 +114,29 @@
client.reportRelaunch(r);
}
- // ObjectPoolItem implementation
-
- private ActivityRelaunchItem() {}
-
- /** Obtain an instance initialized with provided params. */
- @NonNull
- public static ActivityRelaunchItem obtain(@NonNull IBinder activityToken,
- @Nullable List<ResultInfo> pendingResults,
- @Nullable List<ReferrerIntent> pendingNewIntents, int configChanges,
- @NonNull MergedConfiguration config, boolean preserveWindow,
- @NonNull ActivityWindowInfo activityWindowInfo) {
- ActivityRelaunchItem instance = ObjectPool.obtain(ActivityRelaunchItem.class);
- if (instance == null) {
- instance = new ActivityRelaunchItem();
- }
- instance.setActivityToken(activityToken);
- instance.mPendingResults = pendingResults != null ? new ArrayList<>(pendingResults) : null;
- instance.mPendingNewIntents =
- pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null;
- instance.mConfigChanges = configChanges;
- instance.mConfig = new MergedConfiguration(config);
- instance.mPreserveWindow = preserveWindow;
- instance.mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo);
-
- return instance;
- }
-
- @Override
- public void recycle() {
- super.recycle();
- mPendingResults = null;
- mPendingNewIntents = null;
- mConfigChanges = 0;
- mConfig = null;
- mPreserveWindow = false;
- mActivityClientRecord = null;
- mActivityWindowInfo = null;
- ObjectPool.recycle(this);
- }
-
-
// Parcelable implementation
- /** Write to Parcel. */
+ /** Writes to Parcel. */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeTypedList(mPendingResults, flags);
dest.writeTypedList(mPendingNewIntents, flags);
- dest.writeInt(mConfigChanges);
dest.writeTypedObject(mConfig, flags);
- dest.writeBoolean(mPreserveWindow);
dest.writeTypedObject(mActivityWindowInfo, flags);
+ dest.writeInt(mConfigChanges);
+ dest.writeBoolean(mPreserveWindow);
}
- /** Read from Parcel. */
+ /** Reads from Parcel. */
private ActivityRelaunchItem(@NonNull Parcel in) {
super(in);
mPendingResults = in.createTypedArrayList(ResultInfo.CREATOR);
mPendingNewIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
+ mConfig = requireNonNull(in.readTypedObject(MergedConfiguration.CREATOR));
+ mActivityWindowInfo = requireNonNull(in.readTypedObject(ActivityWindowInfo.CREATOR));
mConfigChanges = in.readInt();
- mConfig = in.readTypedObject(MergedConfiguration.CREATOR);
mPreserveWindow = in.readBoolean();
- mActivityWindowInfo = in.readTypedObject(ActivityWindowInfo.CREATOR);
}
public static final @NonNull Creator<ActivityRelaunchItem> CREATOR =
@@ -175,9 +161,10 @@
final ActivityRelaunchItem other = (ActivityRelaunchItem) o;
return Objects.equals(mPendingResults, other.mPendingResults)
&& Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
- && mConfigChanges == other.mConfigChanges && Objects.equals(mConfig, other.mConfig)
- && mPreserveWindow == other.mPreserveWindow
- && Objects.equals(mActivityWindowInfo, other.mActivityWindowInfo);
+ && Objects.equals(mConfig, other.mConfig)
+ && Objects.equals(mActivityWindowInfo, other.mActivityWindowInfo)
+ && mConfigChanges == other.mConfigChanges
+ && mPreserveWindow == other.mPreserveWindow;
}
@Override
@@ -186,10 +173,10 @@
result = 31 * result + super.hashCode();
result = 31 * result + Objects.hashCode(mPendingResults);
result = 31 * result + Objects.hashCode(mPendingNewIntents);
- result = 31 * result + mConfigChanges;
result = 31 * result + Objects.hashCode(mConfig);
- result = 31 * result + (mPreserveWindow ? 1 : 0);
result = 31 * result + Objects.hashCode(mActivityWindowInfo);
+ result = 31 * result + mConfigChanges;
+ result = 31 * result + (mPreserveWindow ? 1 : 0);
return result;
}
@@ -198,9 +185,9 @@
return "ActivityRelaunchItem{" + super.toString()
+ ",pendingResults=" + mPendingResults
+ ",pendingNewIntents=" + mPendingNewIntents
- + ",configChanges=" + mConfigChanges
+ ",config=" + mConfig
- + ",preserveWindow=" + mPreserveWindow
- + ",activityWindowInfo=" + mActivityWindowInfo + "}";
+ + ",activityWindowInfo=" + mActivityWindowInfo
+ + ",configChanges=" + mConfigChanges
+ + ",preserveWindow=" + mPreserveWindow + "}";
}
}
diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java
index 51a09fb..761c519 100644
--- a/core/java/android/app/servertransaction/ActivityResultItem.java
+++ b/core/java/android/app/servertransaction/ActivityResultItem.java
@@ -41,10 +41,13 @@
/**
* Activity result delivery callback.
+ *
* @hide
*/
public class ActivityResultItem extends ActivityTransactionItem {
+ // TODO(b/170729553): Mark this with @NonNull and final once @UnsupportedAppUsage removed.
+ // We cannot do it now to avoid app compatibility regression.
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private List<ResultInfo> mResultInfoList;
@@ -56,6 +59,12 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
public static final long CALL_ACTIVITY_RESULT_BEFORE_RESUME = 78294732L;
+ public ActivityResultItem(@NonNull IBinder activityToken,
+ @NonNull List<ResultInfo> resultInfoList) {
+ super(activityToken);
+ mResultInfoList = new ArrayList<>(resultInfoList);
+ }
+
@Override
public int getPostExecutionState() {
return CompatChanges.isChangeEnabled(CALL_ACTIVITY_RESULT_BEFORE_RESUME)
@@ -70,43 +79,19 @@
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
- // ObjectPoolItem implementation
-
- private ActivityResultItem() {}
-
- /** Obtain an instance initialized with provided params. */
- @NonNull
- public static ActivityResultItem obtain(@NonNull IBinder activityToken,
- @NonNull List<ResultInfo> resultInfoList) {
- ActivityResultItem instance = ObjectPool.obtain(ActivityResultItem.class);
- if (instance == null) {
- instance = new ActivityResultItem();
- }
- instance.setActivityToken(activityToken);
- instance.mResultInfoList = new ArrayList<>(resultInfoList);
-
- return instance;
- }
-
- @Override
- public void recycle() {
- super.recycle();
- mResultInfoList = null;
- ObjectPool.recycle(this);
- }
-
// Parcelable implementation
- /** Write to Parcel. */
+ /** Writes to Parcel. */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeTypedList(mResultInfoList, flags);
}
- /** Read from Parcel. */
+ /** Reads from Parcel. */
private ActivityResultItem(@NonNull Parcel in) {
super(in);
+ // TODO(b/170729553): Wrap with requireNonNull once @UnsupportedAppUsage removed.
mResultInfoList = in.createTypedArrayList(ResultInfo.CREATOR);
}
diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java
index 8ef86ee..506a158 100644
--- a/core/java/android/app/servertransaction/ActivityTransactionItem.java
+++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java
@@ -49,30 +49,13 @@
public abstract class ActivityTransactionItem extends ClientTransactionItem {
/** Target client activity. */
- // TODO(b/311089192): Mark this with @NonNull and final.
- private IBinder mActivityToken;
+ @NonNull
+ private final IBinder mActivityToken;
public ActivityTransactionItem(@NonNull IBinder activityToken) {
mActivityToken = requireNonNull(activityToken);
}
- // TODO(b/311089192): Remove this method once no subclasses obtain from the object pool.
- @Deprecated
- ActivityTransactionItem() {
- }
-
- /**
- * Sets the activity token after the instance is obtained from the object pool.
- *
- * @deprecated This method is deprecated. The object pool is no longer used.
- * Instead, directly create a new object with the activity token.
- * TODO(b/311089192): Remove this method once no subclasses obtain from the object pool.
- */
- @Deprecated
- void setActivityToken(@NonNull IBinder activityToken) {
- mActivityToken = requireNonNull(activityToken);
- }
-
@Override
public final void execute(@NonNull ClientTransactionHandler client,
@NonNull PendingTransactionActions pendingActions) {
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/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
index 22da706..123d792 100644
--- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
@@ -16,6 +16,8 @@
package android.app.servertransaction;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ClientTransactionHandler;
@@ -27,12 +29,20 @@
/**
* App configuration change message.
+ *
* @hide
*/
public class ConfigurationChangeItem extends ClientTransactionItem {
- private Configuration mConfiguration;
- private int mDeviceId;
+ @NonNull
+ private final Configuration mConfiguration;
+
+ private final int mDeviceId;
+
+ public ConfigurationChangeItem(@NonNull Configuration config, int deviceId) {
+ mConfiguration = new Configuration(config);
+ mDeviceId = deviceId;
+ }
@Override
public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -46,55 +56,31 @@
client.handleConfigurationChanged(mConfiguration, mDeviceId);
}
- // ObjectPoolItem implementation
-
- private ConfigurationChangeItem() {}
-
- /** Obtain an instance initialized with provided params. */
- public static ConfigurationChangeItem obtain(@NonNull Configuration config, int deviceId) {
- ConfigurationChangeItem instance = ObjectPool.obtain(ConfigurationChangeItem.class);
- if (instance == null) {
- instance = new ConfigurationChangeItem();
- }
- instance.mConfiguration = new Configuration(config);
- instance.mDeviceId = deviceId;
-
- return instance;
- }
-
- @Override
- public void recycle() {
- mConfiguration = null;
- mDeviceId = 0;
- ObjectPool.recycle(this);
- }
-
-
// Parcelable implementation
- /** Write to Parcel. */
+ /** Writes to Parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedObject(mConfiguration, flags);
dest.writeInt(mDeviceId);
}
- /** Read from Parcel. */
+ /** Reads from Parcel. */
private ConfigurationChangeItem(Parcel in) {
- mConfiguration = in.readTypedObject(Configuration.CREATOR);
+ mConfiguration = requireNonNull(in.readTypedObject(Configuration.CREATOR));
mDeviceId = in.readInt();
}
public static final @android.annotation.NonNull Creator<ConfigurationChangeItem> CREATOR =
- new Creator<ConfigurationChangeItem>() {
- public ConfigurationChangeItem createFromParcel(Parcel in) {
- return new ConfigurationChangeItem(in);
- }
+ new Creator<>() {
+ public ConfigurationChangeItem createFromParcel(Parcel in) {
+ return new ConfigurationChangeItem(in);
+ }
- public ConfigurationChangeItem[] newArray(int size) {
- return new ConfigurationChangeItem[size];
- }
- };
+ public ConfigurationChangeItem[] newArray(int size) {
+ return new ConfigurationChangeItem[size];
+ }
+ };
@Override
public boolean equals(@Nullable Object o) {
diff --git a/core/java/android/app/servertransaction/EnterPipRequestedItem.java b/core/java/android/app/servertransaction/EnterPipRequestedItem.java
index 743653f..b6f8655 100644
--- a/core/java/android/app/servertransaction/EnterPipRequestedItem.java
+++ b/core/java/android/app/servertransaction/EnterPipRequestedItem.java
@@ -24,39 +24,24 @@
/**
* Request an activity to enter picture-in-picture mode.
+ *
* @hide
*/
public final class EnterPipRequestedItem extends ActivityTransactionItem {
+ public EnterPipRequestedItem(@NonNull IBinder activityToken) {
+ super(activityToken);
+ }
+
@Override
public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@NonNull PendingTransactionActions pendingActions) {
client.handlePictureInPictureRequested(r);
}
- // ObjectPoolItem implementation
-
- private EnterPipRequestedItem() {}
-
- /** Obtain an instance initialized with provided params. */
- @NonNull
- public static EnterPipRequestedItem obtain(@NonNull IBinder activityToken) {
- EnterPipRequestedItem instance = ObjectPool.obtain(EnterPipRequestedItem.class);
- if (instance == null) {
- instance = new EnterPipRequestedItem();
- }
- instance.setActivityToken(activityToken);
- return instance;
- }
-
- @Override
- public void recycle() {
- super.recycle();
- ObjectPool.recycle(this);
- }
-
// Parcelable implementation
+ /** Reads from Parcel. */
private EnterPipRequestedItem(@NonNull Parcel in) {
super(in);
}
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 7819e1e..235a9f7 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -20,9 +20,12 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityClient;
+import android.app.ActivityManager.ProcessState;
import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
@@ -52,41 +55,148 @@
/**
* Request to launch an activity.
+ *
* @hide
*/
public class LaunchActivityItem extends ClientTransactionItem {
- private IBinder mActivityToken;
+ @NonNull
+ private final IBinder mActivityToken;
+
+ // TODO(b/170729553): Mark this with @NonNull and final once @UnsupportedAppUsage removed.
+ // We cannot do it now to avoid app compatibility regression.
@UnsupportedAppUsage
private Intent mIntent;
- private int mIdent;
+
+ // TODO(b/170729553): Mark this with @NonNull and final once @UnsupportedAppUsage removed.
+ // We cannot do it now to avoid app compatibility regression.
@UnsupportedAppUsage
private ActivityInfo mInfo;
- private Configuration mCurConfig;
- private Configuration mOverrideConfig;
- private int mDeviceId;
- private String mReferrer;
- private IVoiceInteractor mVoiceInteractor;
- private int mProcState;
- private Bundle mState;
- private PersistableBundle mPersistentState;
- private List<ResultInfo> mPendingResults;
- private List<ReferrerIntent> mPendingNewIntents;
- private SceneTransitionInfo mSceneTransitionInfo;
- private boolean mIsForward;
- private ProfilerInfo mProfilerInfo;
- private IBinder mAssistToken;
- private IBinder mShareableActivityToken;
- private boolean mLaunchedFromBubble;
- private IBinder mTaskFragmentToken;
- private IBinder mInitialCallerInfoAccessToken;
- private ActivityWindowInfo mActivityWindowInfo;
+
+ @NonNull
+ private final Configuration mCurConfig;
+
+ @NonNull
+ private final Configuration mOverrideConfig;
+
+ @Nullable
+ private final String mReferrer;
+
+ @Nullable
+ private final IVoiceInteractor mVoiceInteractor;
+
+ @Nullable
+ private final Bundle mState;
+
+ @Nullable
+ private final PersistableBundle mPersistentState;
+
+ @Nullable
+ private final List<ResultInfo> mPendingResults;
+
+ @Nullable
+ private final List<ReferrerIntent> mPendingNewIntents;
+
+ @Nullable
+ private final SceneTransitionInfo mSceneTransitionInfo;
+
+ @Nullable
+ private final ProfilerInfo mProfilerInfo;
+
+ @NonNull
+ private final IBinder mAssistToken;
+
+ @NonNull
+ private final IBinder mShareableActivityToken;
+
+ @Nullable
+ private final IBinder mTaskFragmentToken;
+
+ @NonNull
+ private final IBinder mInitialCallerInfoAccessToken;
+
+ @NonNull
+ private final ActivityWindowInfo mActivityWindowInfo;
/**
* It is only non-null if the process is the first time to launch activity. It is only an
* optimization for quick look up of the interface so the field is ignored for comparison.
*/
- private IActivityClientController mActivityClientController;
+ @Nullable
+ private final IActivityClientController mActivityClientController;
+
+ private final int mIdent;
+ private final int mDeviceId;
+ private final int mProcState;
+ private final boolean mIsForward;
+ private final boolean mLaunchedFromBubble;
+
+ public LaunchActivityItem(@NonNull IBinder activityToken, @NonNull Intent intent,
+ int ident, @NonNull ActivityInfo info, @NonNull Configuration curConfig,
+ @NonNull Configuration overrideConfig, int deviceId, @Nullable String referrer,
+ @Nullable IVoiceInteractor voiceInteractor, @ProcessState int procState,
+ @Nullable Bundle state, @Nullable PersistableBundle persistentState,
+ @Nullable List<ResultInfo> pendingResults,
+ @Nullable List<ReferrerIntent> pendingNewIntents,
+ @Nullable SceneTransitionInfo sceneTransitionInfo,
+ boolean isForward, @Nullable ProfilerInfo profilerInfo, @NonNull IBinder assistToken,
+ @Nullable IActivityClientController activityClientController,
+ @NonNull IBinder shareableActivityToken, boolean launchedFromBubble,
+ @Nullable IBinder taskFragmentToken, @NonNull IBinder initialCallerInfoAccessToken,
+ @NonNull ActivityWindowInfo activityWindowInfo) {
+ this(activityToken, ident, new Configuration(curConfig), new Configuration(overrideConfig),
+ deviceId, referrer, voiceInteractor, procState,
+ state != null ? new Bundle(state) : null,
+ persistentState != null ? new PersistableBundle(persistentState) : null,
+ pendingResults != null ? new ArrayList<>(pendingResults) : null,
+ pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null,
+ sceneTransitionInfo, isForward,
+ profilerInfo != null ? new ProfilerInfo(profilerInfo) : null,
+ assistToken, activityClientController, shareableActivityToken, launchedFromBubble,
+ taskFragmentToken, initialCallerInfoAccessToken,
+ new ActivityWindowInfo(activityWindowInfo));
+ mIntent = new Intent(intent);
+ mInfo = new ActivityInfo(info);
+ }
+
+ // TODO(b/170729553): Merge this constructor with previous one if no @UnsupportedAppUsage filed.
+ // We cannot do it now to avoid app compatibility regression.
+ private LaunchActivityItem(@NonNull IBinder activityToken, int ident,
+ @NonNull Configuration curConfig,
+ @NonNull Configuration overrideConfig, int deviceId, @Nullable String referrer,
+ @Nullable IVoiceInteractor voiceInteractor, @ProcessState int procState,
+ @Nullable Bundle state, @Nullable PersistableBundle persistentState,
+ @Nullable List<ResultInfo> pendingResults,
+ @Nullable List<ReferrerIntent> pendingNewIntents,
+ @Nullable SceneTransitionInfo sceneTransitionInfo,
+ boolean isForward, @Nullable ProfilerInfo profilerInfo, @NonNull IBinder assistToken,
+ @Nullable IActivityClientController activityClientController,
+ @NonNull IBinder shareableActivityToken, boolean launchedFromBubble,
+ @Nullable IBinder taskFragmentToken, @NonNull IBinder initialCallerInfoAccessToken,
+ @NonNull ActivityWindowInfo activityWindowInfo) {
+ mActivityToken = activityToken;
+ mIdent = ident;
+ mCurConfig = curConfig;
+ mOverrideConfig = overrideConfig;
+ mDeviceId = deviceId;
+ mReferrer = referrer;
+ mVoiceInteractor = voiceInteractor;
+ mProcState = procState;
+ mState = state;
+ mPersistentState = persistentState;
+ mPendingResults = pendingResults;
+ mPendingNewIntents = pendingNewIntents;
+ mSceneTransitionInfo = sceneTransitionInfo;
+ mIsForward = isForward;
+ mProfilerInfo = profilerInfo;
+ mAssistToken = assistToken;
+ mActivityClientController = activityClientController;
+ mShareableActivityToken = shareableActivityToken;
+ mLaunchedFromBubble = launchedFromBubble;
+ mTaskFragmentToken = taskFragmentToken;
+ mInitialCallerInfoAccessToken = initialCallerInfoAccessToken;
+ mActivityWindowInfo = activityWindowInfo;
+ }
@Override
public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -119,44 +229,6 @@
client.countLaunchingActivities(-1);
}
- // ObjectPoolItem implementation
-
- private LaunchActivityItem() {}
-
- /** Obtain an instance initialized with provided params. */
- @NonNull
- public static LaunchActivityItem obtain(@NonNull IBinder activityToken, @NonNull Intent intent,
- int ident, @NonNull ActivityInfo info, @NonNull Configuration curConfig,
- @NonNull Configuration overrideConfig, int deviceId, @Nullable String referrer,
- @Nullable IVoiceInteractor voiceInteractor, int procState, @Nullable Bundle state,
- @Nullable PersistableBundle persistentState, @Nullable List<ResultInfo> pendingResults,
- @Nullable List<ReferrerIntent> pendingNewIntents,
- @Nullable SceneTransitionInfo sceneTransitionInfo,
- boolean isForward, @Nullable ProfilerInfo profilerInfo, @NonNull IBinder assistToken,
- @Nullable IActivityClientController activityClientController,
- @NonNull IBinder shareableActivityToken, boolean launchedFromBubble,
- @Nullable IBinder taskFragmentToken, @NonNull IBinder initialCallerInfoAccessToken,
- @NonNull ActivityWindowInfo activityWindowInfo) {
- LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class);
- if (instance == null) {
- instance = new LaunchActivityItem();
- }
- setValues(instance, activityToken, new Intent(intent), ident, new ActivityInfo(info),
- new Configuration(curConfig), new Configuration(overrideConfig), deviceId,
- referrer, voiceInteractor, procState,
- state != null ? new Bundle(state) : null,
- persistentState != null ? new PersistableBundle(persistentState) : null,
- pendingResults != null ? new ArrayList<>(pendingResults) : null,
- pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null,
- sceneTransitionInfo, isForward,
- profilerInfo != null ? new ProfilerInfo(profilerInfo) : null,
- assistToken, activityClientController, shareableActivityToken,
- launchedFromBubble, taskFragmentToken, initialCallerInfoAccessToken,
- new ActivityWindowInfo(activityWindowInfo));
-
- return instance;
- }
-
@VisibleForTesting(visibility = PACKAGE)
@NonNull
@Override
@@ -164,22 +236,13 @@
return mActivityToken;
}
- @Override
- public void recycle() {
- setValues(this, null, null, 0, null, null, null, 0, null, null, 0, null, null, null, null,
- null, false, null, null, null, null, false, null, null, null);
- ObjectPool.recycle(this);
- }
-
// Parcelable implementation
- /** Write from Parcel. */
+ /** Writes to Parcel. */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStrongBinder(mActivityToken);
- dest.writeTypedObject(mIntent, flags);
dest.writeInt(mIdent);
- dest.writeTypedObject(mInfo, flags);
dest.writeTypedObject(mCurConfig, flags);
dest.writeTypedObject(mOverrideConfig, flags);
dest.writeInt(mDeviceId);
@@ -200,28 +263,40 @@
dest.writeStrongBinder(mTaskFragmentToken);
dest.writeStrongBinder(mInitialCallerInfoAccessToken);
dest.writeTypedObject(mActivityWindowInfo, flags);
+
+ dest.writeTypedObject(mIntent, flags);
+ dest.writeTypedObject(mInfo, flags);
}
- /** Read from Parcel. */
+ /** Reads from Parcel. */
private LaunchActivityItem(@NonNull Parcel in) {
- setValues(this, in.readStrongBinder(), in.readTypedObject(Intent.CREATOR), in.readInt(),
- in.readTypedObject(ActivityInfo.CREATOR), in.readTypedObject(Configuration.CREATOR),
- in.readTypedObject(Configuration.CREATOR), in.readInt(), in.readString(),
- IVoiceInteractor.Stub.asInterface(in.readStrongBinder()), in.readInt(),
- in.readBundle(getClass().getClassLoader()),
- in.readPersistableBundle(getClass().getClassLoader()),
- in.createTypedArrayList(ResultInfo.CREATOR),
- in.createTypedArrayList(ReferrerIntent.CREATOR),
- in.readTypedObject(SceneTransitionInfo.CREATOR),
- in.readBoolean(),
- in.readTypedObject(ProfilerInfo.CREATOR),
- in.readStrongBinder(),
- IActivityClientController.Stub.asInterface(in.readStrongBinder()),
- in.readStrongBinder(),
- in.readBoolean(),
- in.readStrongBinder(),
- in.readStrongBinder(),
- in.readTypedObject(ActivityWindowInfo.CREATOR));
+ this(in.readStrongBinder() /* activityToken */,
+ in.readInt() /* ident */,
+ requireNonNull(in.readTypedObject(Configuration.CREATOR)) /* curConfig */,
+ requireNonNull(in.readTypedObject(Configuration.CREATOR)) /* overrideConfig */,
+ in.readInt() /* deviceId */,
+ in.readString() /* referrer */,
+ IVoiceInteractor.Stub.asInterface(in.readStrongBinder()) /* voiceInteractor */,
+ in.readInt() /* procState */,
+ in.readBundle(in.getClass().getClassLoader()) /* state */,
+ in.readPersistableBundle(in.getClass().getClassLoader()) /* persistentState */,
+ in.createTypedArrayList(ResultInfo.CREATOR) /* pendingResults */,
+ in.createTypedArrayList(ReferrerIntent.CREATOR) /* pendingNewIntents */,
+ in.readTypedObject(SceneTransitionInfo.CREATOR) /* sceneTransitionInfo */,
+ in.readBoolean() /* isForward */,
+ in.readTypedObject(ProfilerInfo.CREATOR) /* profilerInfo */,
+ in.readStrongBinder() /* assistToken */,
+ IActivityClientController.Stub.asInterface(
+ in.readStrongBinder()) /* activityClientController */,
+ in.readStrongBinder() /* shareableActivityToken */,
+ in.readBoolean() /* launchedFromBubble */,
+ in.readStrongBinder() /* taskFragmentToken */,
+ in.readStrongBinder() /* initialCallerInfoAccessToken */,
+ requireNonNull(in.readTypedObject(ActivityWindowInfo.CREATOR))
+ /* activityWindowInfo */
+ );
+ mIntent = in.readTypedObject(Intent.CREATOR);
+ mInfo = in.readTypedObject(ActivityInfo.CREATOR);
}
public static final @NonNull Creator<LaunchActivityItem> CREATOR = new Creator<>() {
@@ -339,45 +414,4 @@
+ ",activityWindowInfo=" + mActivityWindowInfo
+ "}";
}
-
- // Using the same method to set and clear values to make sure we don't forget anything
- private static void setValues(@Nullable LaunchActivityItem instance,
- @Nullable IBinder activityToken, @Nullable Intent intent, int ident,
- @Nullable ActivityInfo info, @Nullable Configuration curConfig,
- @Nullable Configuration overrideConfig, int deviceId,
- @Nullable String referrer, @Nullable IVoiceInteractor voiceInteractor,
- int procState, @Nullable Bundle state, @Nullable PersistableBundle persistentState,
- @Nullable List<ResultInfo> pendingResults,
- @Nullable List<ReferrerIntent> pendingNewIntents,
- @Nullable SceneTransitionInfo sceneTransitionInfo, boolean isForward,
- @Nullable ProfilerInfo profilerInfo, @Nullable IBinder assistToken,
- @Nullable IActivityClientController activityClientController,
- @Nullable IBinder shareableActivityToken, boolean launchedFromBubble,
- @Nullable IBinder taskFragmentToken, @Nullable IBinder initialCallerInfoAccessToken,
- @Nullable ActivityWindowInfo activityWindowInfo) {
- instance.mActivityToken = activityToken;
- instance.mIntent = intent;
- instance.mIdent = ident;
- instance.mInfo = info;
- instance.mCurConfig = curConfig;
- instance.mOverrideConfig = overrideConfig;
- instance.mDeviceId = deviceId;
- instance.mReferrer = referrer;
- instance.mVoiceInteractor = voiceInteractor;
- instance.mProcState = procState;
- instance.mState = state;
- instance.mPersistentState = persistentState;
- instance.mPendingResults = pendingResults;
- instance.mPendingNewIntents = pendingNewIntents;
- instance.mSceneTransitionInfo = sceneTransitionInfo;
- instance.mIsForward = isForward;
- instance.mProfilerInfo = profilerInfo;
- instance.mAssistToken = assistToken;
- instance.mActivityClientController = activityClientController;
- instance.mShareableActivityToken = shareableActivityToken;
- instance.mLaunchedFromBubble = launchedFromBubble;
- instance.mTaskFragmentToken = taskFragmentToken;
- instance.mInitialCallerInfoAccessToken = initialCallerInfoAccessToken;
- instance.mActivityWindowInfo = activityWindowInfo;
- }
}
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index 8706edd..1aa563a 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -18,6 +18,8 @@
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
@@ -33,13 +35,26 @@
/**
* Activity move to a different display message.
+ *
* @hide
*/
public class MoveToDisplayItem extends ActivityTransactionItem {
- private int mTargetDisplayId;
- private Configuration mConfiguration;
- private ActivityWindowInfo mActivityWindowInfo;
+ private final int mTargetDisplayId;
+
+ @NonNull
+ private final Configuration mConfiguration;
+
+ @NonNull
+ private final ActivityWindowInfo mActivityWindowInfo;
+
+ public MoveToDisplayItem(@NonNull IBinder activityToken, int targetDisplayId,
+ @NonNull Configuration configuration, @NonNull ActivityWindowInfo activityWindowInfo) {
+ super(activityToken);
+ mTargetDisplayId = targetDisplayId;
+ mConfiguration = new Configuration(configuration);
+ mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo);
+ }
@Override
public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -58,38 +73,9 @@
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
- // ObjectPoolItem implementation
-
- private MoveToDisplayItem() {}
-
- /** Obtain an instance initialized with provided params. */
- @NonNull
- public static MoveToDisplayItem obtain(@NonNull IBinder activityToken, int targetDisplayId,
- @NonNull Configuration configuration, @NonNull ActivityWindowInfo activityWindowInfo) {
- MoveToDisplayItem instance = ObjectPool.obtain(MoveToDisplayItem.class);
- if (instance == null) {
- instance = new MoveToDisplayItem();
- }
- instance.setActivityToken(activityToken);
- instance.mTargetDisplayId = targetDisplayId;
- instance.mConfiguration = new Configuration(configuration);
- instance.mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo);
-
- return instance;
- }
-
- @Override
- public void recycle() {
- super.recycle();
- mTargetDisplayId = 0;
- mConfiguration = null;
- mActivityWindowInfo = null;
- ObjectPool.recycle(this);
- }
-
// Parcelable implementation
- /** Write to Parcel. */
+ /** Writes to Parcel. */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
@@ -98,12 +84,12 @@
dest.writeTypedObject(mActivityWindowInfo, flags);
}
- /** Read from Parcel. */
+ /** Reads from Parcel. */
private MoveToDisplayItem(@NonNull Parcel in) {
super(in);
mTargetDisplayId = in.readInt();
- mConfiguration = in.readTypedObject(Configuration.CREATOR);
- mActivityWindowInfo = in.readTypedObject(ActivityWindowInfo.CREATOR);
+ mConfiguration = requireNonNull(in.readTypedObject(Configuration.CREATOR));
+ mActivityWindowInfo = requireNonNull(in.readTypedObject(ActivityWindowInfo.CREATOR));
}
public static final @NonNull Creator<MoveToDisplayItem> CREATOR = new Creator<>() {
diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java
index acf2ea4..b5e9d66 100644
--- a/core/java/android/app/servertransaction/NewIntentItem.java
+++ b/core/java/android/app/servertransaction/NewIntentItem.java
@@ -38,13 +38,24 @@
/**
* New intent message.
+ *
* @hide
*/
public class NewIntentItem extends ActivityTransactionItem {
+ // TODO(b/170729553): Mark this with @NonNull and final once @UnsupportedAppUsage removed.
+ // We cannot do it now to avoid app compatibility regression.
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private List<ReferrerIntent> mIntents;
- private boolean mResume;
+
+ private final boolean mResume;
+
+ public NewIntentItem(@NonNull IBinder activityToken,
+ @NonNull List<ReferrerIntent> intents, boolean resume) {
+ super(activityToken);
+ mIntents = new ArrayList<>(intents);
+ mResume = resume;
+ }
@Override
public int getPostExecutionState() {
@@ -59,36 +70,9 @@
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
- // ObjectPoolItem implementation
-
- private NewIntentItem() {}
-
- /** Obtain an instance initialized with provided params. */
- @NonNull
- public static NewIntentItem obtain(@NonNull IBinder activityToken,
- @NonNull List<ReferrerIntent> intents, boolean resume) {
- NewIntentItem instance = ObjectPool.obtain(NewIntentItem.class);
- if (instance == null) {
- instance = new NewIntentItem();
- }
- instance.setActivityToken(activityToken);
- instance.mIntents = new ArrayList<>(intents);
- instance.mResume = resume;
-
- return instance;
- }
-
- @Override
- public void recycle() {
- super.recycle();
- mIntents = null;
- mResume = false;
- ObjectPool.recycle(this);
- }
-
// Parcelable implementation
- /** Write to Parcel. */
+ /** Writes to Parcel. */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
@@ -96,10 +80,11 @@
dest.writeTypedList(mIntents, flags);
}
- /** Read from Parcel. */
+ /** Reads from Parcel. */
private NewIntentItem(@NonNull Parcel in) {
super(in);
mResume = in.readBoolean();
+ // TODO(b/170729553): Wrap with requireNonNull once @UnsupportedAppUsage removed.
mIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
}
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/PipStateTransactionItem.java b/core/java/android/app/servertransaction/PipStateTransactionItem.java
index 30289ef..ddeb2c1 100644
--- a/core/java/android/app/servertransaction/PipStateTransactionItem.java
+++ b/core/java/android/app/servertransaction/PipStateTransactionItem.java
@@ -28,11 +28,19 @@
/**
* Request an activity to enter picture-in-picture mode.
+ *
* @hide
*/
public final class PipStateTransactionItem extends ActivityTransactionItem {
- private PictureInPictureUiState mPipState;
+ @NonNull
+ private final PictureInPictureUiState mPipState;
+
+ public PipStateTransactionItem(@NonNull IBinder activityToken,
+ @NonNull PictureInPictureUiState pipState) {
+ super(activityToken);
+ mPipState = pipState;
+ }
@Override
public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@@ -40,41 +48,16 @@
client.handlePictureInPictureStateChanged(r, mPipState);
}
- // ObjectPoolItem implementation
-
- private PipStateTransactionItem() {}
-
- /** Obtain an instance initialized with provided params. */
- @NonNull
- public static PipStateTransactionItem obtain(@NonNull IBinder activityToken,
- @NonNull PictureInPictureUiState pipState) {
- PipStateTransactionItem instance = ObjectPool.obtain(PipStateTransactionItem.class);
- if (instance == null) {
- instance = new PipStateTransactionItem();
- }
- instance.setActivityToken(activityToken);
- instance.mPipState = pipState;
-
- return instance;
- }
-
- @Override
- public void recycle() {
- super.recycle();
- mPipState = null;
- ObjectPool.recycle(this);
- }
-
// Parcelable implementation
- /** Write to Parcel. */
+ /** Writes to Parcel. */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
mPipState.writeToParcel(dest, flags);
}
- /** Read from Parcel. */
+ /** Reads from Parcel. */
private PipStateTransactionItem(@NonNull Parcel in) {
super(in);
mPipState = PictureInPictureUiState.CREATOR.createFromParcel(in);
diff --git a/core/java/android/app/servertransaction/RefreshCallbackItem.java b/core/java/android/app/servertransaction/RefreshCallbackItem.java
index a3f82e9..57fd97a 100644
--- a/core/java/android/app/servertransaction/RefreshCallbackItem.java
+++ b/core/java/android/app/servertransaction/RefreshCallbackItem.java
@@ -44,7 +44,20 @@
// Whether refresh should happen using the "stopped -> resumed" cycle or
// "paused -> resumed" cycle.
@LifecycleState
- private int mPostExecutionState;
+ private final int mPostExecutionState;
+
+ /**
+ * Creates a new RefreshCallbackItem.
+ *
+ * @param activityToken the target client activity.
+ * @param postExecutionState indicating whether refresh should happen using the
+ * "stopped -> "resumed" cycle or "paused -> resumed" cycle.
+ */
+ public RefreshCallbackItem(
+ @NonNull IBinder activityToken, @LifecycleState int postExecutionState) {
+ super(activityToken);
+ mPostExecutionState = postExecutionState;
+ }
@Override
public void execute(@NonNull ClientTransactionHandler client,
@@ -67,47 +80,21 @@
return false;
}
- // ObjectPoolItem implementation
-
- @Override
- public void recycle() {
- super.recycle();
- ObjectPool.recycle(this);
- }
-
- /**
- * Obtain an instance initialized with provided params.
- * @param postExecutionState indicating whether refresh should happen using the
- * "stopped -> resumed" cycle or "paused -> resumed" cycle.
- */
- @NonNull
- public static RefreshCallbackItem obtain(@NonNull IBinder activityToken,
- @LifecycleState int postExecutionState) {
- if (postExecutionState != ON_STOP && postExecutionState != ON_PAUSE) {
- throw new IllegalArgumentException(
- "Only ON_STOP or ON_PAUSE are allowed as a post execution state for "
- + "RefreshCallbackItem but got " + postExecutionState);
- }
- RefreshCallbackItem instance =
- ObjectPool.obtain(RefreshCallbackItem.class);
- if (instance == null) {
- instance = new RefreshCallbackItem();
- }
- instance.setActivityToken(activityToken);
- instance.mPostExecutionState = postExecutionState;
- return instance;
- }
-
- private RefreshCallbackItem() {}
-
// Parcelable implementation
+ /** Writes to Parcel. */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mPostExecutionState);
}
+ /** Reads from Parcel. */
+ private RefreshCallbackItem(@NonNull Parcel in) {
+ super(in);
+ mPostExecutionState = in.readInt();
+ }
+
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
@@ -134,11 +121,6 @@
+ ",mPostExecutionState=" + mPostExecutionState + "}";
}
- private RefreshCallbackItem(@NonNull Parcel in) {
- super(in);
- mPostExecutionState = in.readInt();
- }
-
public static final @NonNull Creator<RefreshCallbackItem> CREATOR = new Creator<>() {
public RefreshCallbackItem createFromParcel(@NonNull Parcel in) {
diff --git a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
index 23d4505..b5f8345 100644
--- a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
+++ b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.app.servertransaction;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
@@ -28,11 +29,17 @@
/**
* Top resumed activity changed callback.
+ *
* @hide
*/
public class TopResumedActivityChangeItem extends ActivityTransactionItem {
- private boolean mOnTop;
+ private final boolean mOnTop;
+
+ public TopResumedActivityChangeItem(@NonNull IBinder activityToken, boolean onTop) {
+ super(activityToken);
+ mOnTop = onTop;
+ }
@Override
public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@@ -58,42 +65,16 @@
ActivityClient.getInstance().activityTopResumedStateLost();
}
- // ObjectPoolItem implementation
-
- private TopResumedActivityChangeItem() {}
-
- /** Obtain an instance initialized with provided params. */
- @NonNull
- public static TopResumedActivityChangeItem obtain(@NonNull IBinder activityToken,
- boolean onTop) {
- TopResumedActivityChangeItem instance =
- ObjectPool.obtain(TopResumedActivityChangeItem.class);
- if (instance == null) {
- instance = new TopResumedActivityChangeItem();
- }
- instance.setActivityToken(activityToken);
- instance.mOnTop = onTop;
-
- return instance;
- }
-
- @Override
- public void recycle() {
- super.recycle();
- mOnTop = false;
- ObjectPool.recycle(this);
- }
-
// Parcelable implementation
- /** Write to Parcel. */
+ /** Writes to Parcel. */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeBoolean(mOnTop);
}
- /** Read from Parcel. */
+ /** Reads from Parcel. */
private TopResumedActivityChangeItem(@NonNull Parcel in) {
super(in);
mOnTop = in.readBoolean();
@@ -131,7 +112,6 @@
@Override
public String toString() {
- return "TopResumedActivityChangeItem{" + super.toString()
- + ",onTop=" + mOnTop + "}";
+ return "TopResumedActivityChangeItem{" + super.toString() + ",onTop=" + mOnTop + "}";
}
}
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/TransferSplashScreenViewStateItem.java b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
index 11947e9..5068c39 100644
--- a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
+++ b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
@@ -29,12 +29,24 @@
/**
* Transfer a splash screen view to an Activity.
+ *
* @hide
*/
public class TransferSplashScreenViewStateItem extends ActivityTransactionItem {
- private SplashScreenViewParcelable mSplashScreenViewParcelable;
- private SurfaceControl mStartingWindowLeash;
+ @Nullable
+ private final SplashScreenViewParcelable mSplashScreenViewParcelable;
+
+ @Nullable
+ private final SurfaceControl mStartingWindowLeash;
+
+ public TransferSplashScreenViewStateItem(@NonNull IBinder activityToken,
+ @Nullable SplashScreenViewParcelable parcelable,
+ @Nullable SurfaceControl startingWindowLeash) {
+ super(activityToken);
+ mSplashScreenViewParcelable = parcelable;
+ mStartingWindowLeash = startingWindowLeash;
+ }
@Override
public void execute(@NonNull ClientTransactionHandler client,
@@ -43,14 +55,9 @@
client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable, mStartingWindowLeash);
}
- @Override
- public void recycle() {
- super.recycle();
- mSplashScreenViewParcelable = null;
- mStartingWindowLeash = null;
- ObjectPool.recycle(this);
- }
+ // Parcelable implementation
+ /** Writes to Parcel. */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
@@ -58,31 +65,13 @@
dest.writeTypedObject(mStartingWindowLeash, flags);
}
- private TransferSplashScreenViewStateItem() {}
-
+ /** Reads from Parcel. */
private TransferSplashScreenViewStateItem(@NonNull Parcel in) {
super(in);
mSplashScreenViewParcelable = in.readTypedObject(SplashScreenViewParcelable.CREATOR);
mStartingWindowLeash = in.readTypedObject(SurfaceControl.CREATOR);
}
- /** Obtain an instance initialized with provided params. */
- @NonNull
- public static TransferSplashScreenViewStateItem obtain(
- @NonNull IBinder activityToken, @Nullable SplashScreenViewParcelable parcelable,
- @Nullable SurfaceControl startingWindowLeash) {
- TransferSplashScreenViewStateItem instance =
- ObjectPool.obtain(TransferSplashScreenViewStateItem.class);
- if (instance == null) {
- instance = new TransferSplashScreenViewStateItem();
- }
- instance.setActivityToken(activityToken);
- instance.mSplashScreenViewParcelable = parcelable;
- instance.mStartingWindowLeash = startingWindowLeash;
-
- return instance;
- }
-
public static final @NonNull Creator<TransferSplashScreenViewStateItem> CREATOR =
new Creator<>() {
public TransferSplashScreenViewStateItem createFromParcel(@NonNull Parcel in) {
diff --git a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
index f6a7291..b2e87bd 100644
--- a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
+++ b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
@@ -30,14 +30,22 @@
/**
* {@link android.window.WindowContext} configuration change message.
+ *
* @hide
*/
public class WindowContextInfoChangeItem extends ClientTransactionItem {
- @Nullable
- private IBinder mClientToken;
- @Nullable
- private WindowContextInfo mInfo;
+ @NonNull
+ private final IBinder mClientToken;
+
+ @NonNull
+ private final WindowContextInfo mInfo;
+
+ public WindowContextInfoChangeItem(
+ @NonNull IBinder clientToken, @NonNull Configuration config, int displayId) {
+ mClientToken = requireNonNull(clientToken);
+ mInfo = new WindowContextInfo(new Configuration(config), displayId);
+ }
@Override
public void execute(@NonNull ClientTransactionHandler client,
@@ -45,31 +53,6 @@
client.handleWindowContextInfoChanged(mClientToken, mInfo);
}
- // ObjectPoolItem implementation
-
- private WindowContextInfoChangeItem() {}
-
- /** Obtains an instance initialized with provided params. */
- public static WindowContextInfoChangeItem obtain(
- @NonNull IBinder clientToken, @NonNull Configuration config, int displayId) {
- WindowContextInfoChangeItem instance =
- ObjectPool.obtain(WindowContextInfoChangeItem.class);
- if (instance == null) {
- instance = new WindowContextInfoChangeItem();
- }
- instance.mClientToken = requireNonNull(clientToken);
- instance.mInfo = new WindowContextInfo(new Configuration(config), displayId);
-
- return instance;
- }
-
- @Override
- public void recycle() {
- mClientToken = null;
- mInfo = null;
- ObjectPool.recycle(this);
- }
-
// Parcelable implementation
/** Writes to Parcel. */
@@ -82,7 +65,7 @@
/** Reads from Parcel. */
private WindowContextInfoChangeItem(@NonNull Parcel in) {
mClientToken = in.readStrongBinder();
- mInfo = in.readTypedObject(WindowContextInfo.CREATOR);
+ mInfo = requireNonNull(in.readTypedObject(WindowContextInfo.CREATOR));
}
public static final @NonNull Creator<WindowContextInfoChangeItem> CREATOR =
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/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index ce241c1..88fbbdd 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -190,6 +190,18 @@
}
}
+flag {
+ name: "cache_user_serial_number_read_only"
+ namespace: "multiuser"
+ description: "Optimise user serial number retrieval. Read only flag, so that it can be used before the flags are initialized."
+ bug: "353134536"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_fixed_read_only: true
+}
+
+
# This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile.
flag {
name: "enable_private_space_features"
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 6113932..e8d7e1e 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -600,7 +600,7 @@
*
* <p>
* This sensor must be able to detect and report an on-body to off-body
- * transition within 1 second of the device being removed from the body,
+ * transition within 3 seconds of the device being removed from the body,
* and must be able to detect and report an off-body to on-body transition
* within 5 seconds of the device being put back onto the body.
* </p>
diff --git a/core/java/android/hardware/input/VirtualKeyEvent.java b/core/java/android/hardware/input/VirtualKeyEvent.java
index c0102bf..da959af 100644
--- a/core/java/android/hardware/input/VirtualKeyEvent.java
+++ b/core/java/android/hardware/input/VirtualKeyEvent.java
@@ -286,6 +286,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualKeyEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/VirtualMouseButtonEvent.java b/core/java/android/hardware/input/VirtualMouseButtonEvent.java
index fc42b15..333c3c7 100644
--- a/core/java/android/hardware/input/VirtualMouseButtonEvent.java
+++ b/core/java/android/hardware/input/VirtualMouseButtonEvent.java
@@ -197,6 +197,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualMouseButtonEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/VirtualMouseRelativeEvent.java b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
index 2a42cfc..86d759d 100644
--- a/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
+++ b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
@@ -135,6 +135,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualMouseRelativeEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/VirtualMouseScrollEvent.java b/core/java/android/hardware/input/VirtualMouseScrollEvent.java
index c89c188..a4958c7 100644
--- a/core/java/android/hardware/input/VirtualMouseScrollEvent.java
+++ b/core/java/android/hardware/input/VirtualMouseScrollEvent.java
@@ -146,6 +146,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualMouseScrollEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
index 8c98abd..033b1c1 100644
--- a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
+++ b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
@@ -129,6 +129,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualRotaryEncoderScrollEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/VirtualStylusButtonEvent.java b/core/java/android/hardware/input/VirtualStylusButtonEvent.java
index 97a4cd0..8fcf561b 100644
--- a/core/java/android/hardware/input/VirtualStylusButtonEvent.java
+++ b/core/java/android/hardware/input/VirtualStylusButtonEvent.java
@@ -187,6 +187,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualStylusButtonEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/VirtualStylusMotionEvent.java b/core/java/android/hardware/input/VirtualStylusMotionEvent.java
index 2ab76ae..0ac6f3a 100644
--- a/core/java/android/hardware/input/VirtualStylusMotionEvent.java
+++ b/core/java/android/hardware/input/VirtualStylusMotionEvent.java
@@ -377,6 +377,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualStylusMotionEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/VirtualTouchEvent.java b/core/java/android/hardware/input/VirtualTouchEvent.java
index 7936dfe..0cccd25 100644
--- a/core/java/android/hardware/input/VirtualTouchEvent.java
+++ b/core/java/android/hardware/input/VirtualTouchEvent.java
@@ -354,6 +354,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualTouchEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 0cd2800..b4ad050 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -75,3 +75,13 @@
description: "Enables a developer overlay that displays raw touchpad input data and gesture recognition status in real-time."
bug: "286551975"
}
+
+flag {
+ namespace: "input_native"
+ name: "keyboard_layout_manager_multi_user_ime_setup"
+ description: "Update KeyboardLayoutManager to work correctly with multi-user IME setup"
+ bug: "354333072"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index bfff4db..e33a5c9 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -29,6 +29,7 @@
import static java.util.Objects.requireNonNull;
import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -874,10 +875,9 @@
/*****************************************************************************
* A GenericSoundModel is a specialized {@link SoundModel} for non-voice sound
* patterns.
- *
- * @hide
****************************************************************************/
- public static class GenericSoundModel extends SoundModel implements Parcelable {
+ @FlaggedApi(android.media.soundtrigger.Flags.FLAG_SOUND_TRIGGER_GENERIC_MODEL_API)
+ public static final class GenericSoundModel extends SoundModel implements Parcelable {
public static final @android.annotation.NonNull Parcelable.Creator<GenericSoundModel> CREATOR
= new Parcelable.Creator<GenericSoundModel>() {
@@ -890,12 +890,27 @@
}
};
+ /**
+ * Constructor for {@link GenericSoundModel} with version.
+ *
+ * @param uuid Unique identifier for this sound model.
+ * @param vendorUuid Unique vendor identifier for this sound model.
+ * @param data Opaque data for this sound model.
+ * @param version Vendor-specific version number of this sound model.
+ */
public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
@Nullable byte[] data, int version) {
- super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data, version);
+ super(uuid, Objects.requireNonNull(vendorUuid, "vendorUuid cannot be null"),
+ TYPE_GENERIC_SOUND, data, version);
}
- @UnsupportedAppUsage
+ /**
+ * Constructor for {@link GenericSoundModel} without version. The version is set to -1.
+ *
+ * @param uuid Unique identifier for this sound model.
+ * @param vendorUuid Unique vendor identifier for this sound model.
+ * @param data Opaque data for this sound model.
+ */
public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
@Nullable byte[] data) {
this(uuid, vendorUuid, data, -1);
@@ -919,7 +934,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(getUuid().toString());
if (getVendorUuid() == null) {
dest.writeInt(-1);
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 2447ff9..000a537 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -66,6 +66,7 @@
POWER_COMPONENT_WAKELOCK,
POWER_COMPONENT_MEMORY,
POWER_COMPONENT_PHONE,
+ POWER_COMPONENT_AMBIENT_DISPLAY,
POWER_COMPONENT_IDLE,
POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS,
})
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b2f333a..cdffea4 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2491,7 +2491,7 @@
public static final int SCREEN_BRIGHTNESS_LIGHT = 3;
public static final int SCREEN_BRIGHTNESS_BRIGHT = 4;
- static final String[] SCREEN_BRIGHTNESS_NAMES = {
+ public static final String[] SCREEN_BRIGHTNESS_NAMES = {
"dark", "dim", "medium", "light", "bright"
};
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..475984e 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,31 @@
}
/**
+ * 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) {
+ VibrationEffect vendorEffect = new VendorEffect(effect, VendorEffect.DEFAULT_STRENGTH,
+ VendorEffect.DEFAULT_SCALE);
+ vendorEffect.validate();
+ return vendorEffect;
+ }
+
+ /**
* Get a predefined vibration effect.
*
* <p>Predefined effects are a set of common vibration effects that should be identical,
@@ -508,7 +544,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 +586,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 +610,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 +712,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 +761,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 +896,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 +919,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 +972,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 +1014,7 @@
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_COMPOSED);
out.writeList(mSegments);
out.writeInt(mRepeatIndex);
}
@@ -1011,6 +1057,255 @@
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)
+ && isPersistableBundleEquals(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);
+ }
+
+ /**
+ * Compares two {@link PersistableBundle} objects are equals.
+ */
+ private static boolean isPersistableBundleEquals(
+ PersistableBundle first, PersistableBundle second) {
+ if (first == second) {
+ return true;
+ }
+ if (first == null || second == null || first.size() != second.size()) {
+ return false;
+ }
+ for (String key : first.keySet()) {
+ if (!isPersistableBundleSupportedValueEquals(first.get(key), second.get(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Compares two values which type is supported by {@link PersistableBundle}.
+ *
+ * <p>If the type isn't supported. The equality is done by {@link Object#equals(Object)}.
+ */
+ private static boolean isPersistableBundleSupportedValueEquals(
+ Object first, Object second) {
+ if (first == second) {
+ return true;
+ } else if (first == null || second == null
+ || !first.getClass().equals(second.getClass())) {
+ return false;
+ } else if (first instanceof PersistableBundle) {
+ return isPersistableBundleEquals(
+ (PersistableBundle) first, (PersistableBundle) second);
+ } else if (first instanceof int[]) {
+ return Arrays.equals((int[]) first, (int[]) second);
+ } else if (first instanceof long[]) {
+ return Arrays.equals((long[]) first, (long[]) second);
+ } else if (first instanceof double[]) {
+ return Arrays.equals((double[]) first, (double[]) second);
+ } else if (first instanceof boolean[]) {
+ return Arrays.equals((boolean[]) first, (boolean[]) second);
+ } else if (first instanceof String[]) {
+ return Arrays.equals((String[]) first, (String[]) second);
+ } else {
+ return Objects.equals(first, second);
+ }
+ }
+
+ @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 +1544,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 +1582,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 +1927,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.java b/core/java/android/os/Vibrator.java
index 71c83f2..161cce0 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -497,7 +497,27 @@
*/
@RequiresPermission(android.Manifest.permission.VIBRATE)
public void vibrate(@NonNull VibrationEffect vibe, @NonNull VibrationAttributes attributes) {
- vibrate(Process.myUid(), mPackageName, vibe, null, attributes);
+ vibrate(vibe, attributes, null);
+ }
+
+ /**
+ * Vibrate with a given effect.
+ *
+ * <p>The app should be in the foreground for the vibration to happen. Background apps should
+ * specify a ringtone, notification or alarm usage in order to vibrate.</p>
+ *
+ * @param vibe {@link VibrationEffect} describing the vibration to be performed.
+ * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
+ * specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
+ * {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with
+ * incoming calls.
+ * @param reason the reason for this vibration, used for debugging purposes.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public void vibrate(@NonNull VibrationEffect vibe,
+ @NonNull VibrationAttributes attributes, String reason) {
+ vibrate(Process.myUid(), mPackageName, vibe, reason, attributes);
}
/**
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/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 708c196..192afb1 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -28,6 +28,7 @@
import android.annotation.SystemApi;
import android.annotation.UserHandleAware;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -1624,6 +1625,19 @@
"is_call_log_phone_account_migration_pending";
/**
+ * The default maximum number of call log entries stored in the call log provider for each
+ * {@link PhoneAccountHandle}.
+ */
+ private static final int DEFAULT_MAX_CALL_LOG_SIZE = 500;
+
+ /**
+ * Expected component name of Telephony phone accounts.
+ */
+ private static final ComponentName TELEPHONY_COMPONENT_NAME =
+ new ComponentName("com.android.phone",
+ "com.android.services.telephony.TelephonyConnectionService");
+
+ /**
* Adds a call to the call log.
*
* @param ci the CallerInfo object to get the target contact from. Can be null
@@ -2084,25 +2098,35 @@
}
int numDeleted;
- if (values.containsKey(PHONE_ACCOUNT_ID)
- && !TextUtils.isEmpty(values.getAsString(PHONE_ACCOUNT_ID))
- && values.containsKey(PHONE_ACCOUNT_COMPONENT_NAME)
- && !TextUtils.isEmpty(values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME))) {
+ final String phoneAccountId =
+ values.containsKey(PHONE_ACCOUNT_ID)
+ ? values.getAsString(PHONE_ACCOUNT_ID) : null;
+ final String phoneAccountComponentName =
+ values.containsKey(PHONE_ACCOUNT_COMPONENT_NAME)
+ ? values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME) : null;
+ int maxCallLogSize = DEFAULT_MAX_CALL_LOG_SIZE;
+ if (!TextUtils.isEmpty(phoneAccountId)
+ && !TextUtils.isEmpty(phoneAccountComponentName)) {
+ if (android.provider.Flags.allowConfigMaximumCallLogEntriesPerSim()
+ && TELEPHONY_COMPONENT_NAME
+ .flattenToString().equals(phoneAccountComponentName)) {
+ maxCallLogSize = context.getResources().getInteger(
+ com.android.internal.R.integer.config_maximumCallLogEntriesPerSim);
+ }
// Only purge entries for the same phone account.
numDeleted = resolver.delete(uri, "_id IN "
+ "(SELECT _id FROM calls"
+ " WHERE " + PHONE_ACCOUNT_COMPONENT_NAME + " = ?"
+ " AND " + PHONE_ACCOUNT_ID + " = ?"
+ " ORDER BY " + DEFAULT_SORT_ORDER
- + " LIMIT -1 OFFSET 500)", new String[] {
- values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME),
- values.getAsString(PHONE_ACCOUNT_ID)
- });
+ + " LIMIT -1 OFFSET " + maxCallLogSize + ")",
+ new String[] { phoneAccountComponentName, phoneAccountId }
+ );
} else {
// No valid phone account specified, so default to the old behavior.
numDeleted = resolver.delete(uri, "_id IN "
+ "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
- + " LIMIT -1 OFFSET 500)", null);
+ + " LIMIT -1 OFFSET " + maxCallLogSize + ")", null);
}
Log.i(LOG_TAG, "addEntry: cleaned up " + numDeleted + " old entries");
diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig
index ff98fc4..53d0c62 100644
--- a/core/java/android/provider/flags.aconfig
+++ b/core/java/android/provider/flags.aconfig
@@ -30,4 +30,16 @@
namespace: "backstage_power"
description: "Add a new settings page for the RUN_BACKUP_JOBS permission."
bug: "320563660"
-}
\ No newline at end of file
+}
+
+# OWNER = tgunn TARGET=25Q1
+flag {
+ name: "allow_config_maximum_call_log_entries_per_sim"
+ is_exported: true
+ namespace: "telecom"
+ description: "Allow partners to modify the maximum number of call log size for each sim card."
+ bug: "352235494"
+ metadata {
+ purpose: PURPOSE_FEATURE
+ }
+}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index fbeab84..d454716 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -266,9 +266,9 @@
private boolean mDozing;
private boolean mWindowless;
private boolean mPreviewMode;
- private int mDozeScreenState = Display.STATE_UNKNOWN;
- private @Display.StateReason int mDozeScreenStateReason = Display.STATE_REASON_UNKNOWN;
- private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
+ private volatile int mDozeScreenState = Display.STATE_UNKNOWN;
+ private volatile @Display.StateReason int mDozeScreenStateReason = Display.STATE_REASON_UNKNOWN;
+ private volatile int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
private boolean mDebug = false;
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 620eef6..76f6363 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -39,7 +39,9 @@
@UnsupportedAppUsage
boolean isDreamingOrInPreview();
boolean canStartDreaming(boolean isScreenOn);
+ /** @deprecated Please use finishSelfOneway instead. */
void finishSelf(in IBinder token, boolean immediate);
+ /** @deprecated Please use startDozingOneway instead. */
void startDozing(in IBinder token, int screenState, int reason, int screenBrightness);
void stopDozing(in IBinder token);
void forceAmbientDisplayEnabled(boolean enabled);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 46b222b..66d08f9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -404,8 +404,7 @@
}
private void prepareToDraw() {
- if (mDisplayState == Display.STATE_DOZE
- || mDisplayState == Display.STATE_DOZE_SUSPEND) {
+ if (mDisplayState == Display.STATE_DOZE) {
try {
mSession.pokeDrawLock(mWindow);
} catch (RemoteException e) {
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 4281da1..5ac0c50 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1689,6 +1689,10 @@
public final void onCarrierRoamingNtnModeChanged(boolean active) {
// not supported on the deprecated interface - Use TelephonyCallback instead
}
+
+ public final void onCarrierRoamingNtnEligibleStateChanged(boolean eligible) {
+ // not supported on the deprecated interface - Use TelephonyCallback instead
+ }
}
private void log(String s) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index b8b84d9..c360e64 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -653,6 +653,27 @@
public static final int EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED = 42;
/**
+ * Event for listening to changes in carrier roaming non-terrestrial network eligibility.
+ *
+ * @see CarrierRoamingNtnModeListener
+ *
+ * Device is eligible for satellite communication if all the following conditions are met:
+ * <ul>
+ * <li>Any subscription on the device supports P2P satellite messaging which is defined by
+ * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li>
+ * <li>{@link CarrierConfigManager#KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT} set to
+ * {@link CarrierConfigManager#CARRIER_ROAMING_NTN_CONNECT_MANUAL} </li>
+ * <li>The device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi,
+ * and the hysteresis timer defined by {@link CarrierConfigManager
+ * #KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT} is expired.
+ * </li>
+ * </ul>
+ *
+ * @hide
+ */
+ public static final int EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED = 43;
+
+ /**
* @hide
*/
@IntDef(prefix = {"EVENT_"}, value = {
@@ -697,7 +718,8 @@
EVENT_MEDIA_QUALITY_STATUS_CHANGED,
EVENT_EMERGENCY_CALLBACK_MODE_CHANGED,
EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED,
- EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED
+ EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED,
+ EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED
})
@Retention(RetentionPolicy.SOURCE)
public @interface TelephonyEvent {
@@ -1711,6 +1733,23 @@
* {code false} otherwise.
*/
void onCarrierRoamingNtnModeChanged(boolean active);
+
+ /**
+ * Callback invoked when carrier roaming non-terrestrial network eligibility changes.
+ *
+ * @param eligible {@code true} when the device is eligible for satellite
+ * communication if all the following conditions are met:
+ * <ul>
+ * <li>Any subscription on the device supports P2P satellite messaging which is defined by
+ * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li>
+ * <li>{@link CarrierConfigManager#KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT} set to
+ * {@link CarrierConfigManager#CARRIER_ROAMING_NTN_CONNECT_MANUAL} </li>
+ * <li>The device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi,
+ * and the hysteresis timer defined by {@link CarrierConfigManager
+ * #KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT} is expired. </li>
+ * </ul>
+ */
+ default void onCarrierRoamingNtnEligibleStateChanged(boolean eligible) {}
}
/**
@@ -2125,5 +2164,16 @@
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> listener.onCarrierRoamingNtnModeChanged(active)));
}
+
+ public void onCarrierRoamingNtnEligibleStateChanged(boolean eligible) {
+ if (!Flags.carrierRoamingNbIotNtn()) return;
+
+ CarrierRoamingNtnModeListener listener =
+ (CarrierRoamingNtnModeListener) mTelephonyCallbackWeakRef.get();
+ if (listener == null) return;
+
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> listener.onCarrierRoamingNtnEligibleStateChanged(eligible)));
+ }
}
}
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 6160fdb..10f03c1 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -1091,6 +1091,34 @@
}
/**
+ * Notify external listeners that device eligibility to connect to carrier roaming
+ * non-terrestrial network changed.
+ *
+ * @param subId subscription ID.
+ * @param eligible {@code true} when the device is eligible for satellite
+ * communication if all the following conditions are met:
+ * <ul>
+ * <li>Any subscription supports P2P satellite messaging which is defined by
+ * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li>
+ * <li>{@link CarrierConfigManager#KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT} set to
+ * {@link CarrierConfigManager#CARRIER_ROAMING_NTN_CONNECT_MANUAL} </li>
+ * <li>The device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi,
+ * and the hysteresis timer defined by {@link CarrierConfigManager
+ * #KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT} is expired. </li>
+ * </ul>
+ *
+ * @hide
+ */
+ public void notifyCarrierRoamingNtnEligibleStateChanged(int subId, boolean eligible) {
+ try {
+ sRegistry.notifyCarrierRoamingNtnEligibleStateChanged(subId, eligible);
+ } catch (RemoteException ex) {
+ // system server crash
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Processes potential event changes from the provided {@link TelephonyCallback}.
*
* @param telephonyCallback callback for monitoring callback changes to the telephony state.
@@ -1246,6 +1274,11 @@
if (telephonyCallback instanceof TelephonyCallback.CarrierRoamingNtnModeListener) {
eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED);
}
+
+ if (telephonyCallback instanceof TelephonyCallback.CarrierRoamingNtnModeListener) {
+ eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED);
+ }
+
return eventList;
}
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 9db8aa1..4fdcecc 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -30,6 +30,8 @@
import android.graphics.text.LineBreakConfig;
import android.text.style.ParagraphStyle;
+import com.android.text.flags.Flags;
+
/**
* A BoringLayout is a very simple Layout implementation for text that
* fits on a single line and is all left-to-right characters.
@@ -589,7 +591,7 @@
fm.reset();
}
- if (ClientFlags.fixLineHeightForLocale()) {
+ if (Flags.fixLineHeightForLocale()) {
if (minimumFontMetrics != null) {
fm.set(minimumFontMetrics);
// Because the font metrics is provided by public APIs, adjust the top/bottom with
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index b07534f..5d84d17 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -68,11 +68,4 @@
public static boolean fixMisalignedContextMenu() {
return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU);
}
-
- /**
- * @see Flags#clearFontVariationSettings()
- */
- public static boolean clearFontVariationSettings() {
- return TextFlags.isFeatureEnabled(Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS);
- }
}
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index 161a79b..896e087 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -42,6 +42,8 @@
import android.text.style.ReplacementSpan;
import android.util.Pools.SynchronizedPool;
+import com.android.text.flags.Flags;
+
import java.util.Arrays;
/**
@@ -199,7 +201,7 @@
* @hide
*/
public @Layout.Direction int getParagraphDir() {
- if (ClientFlags.icuBidiMigration()) {
+ if (Flags.icuBidiMigration()) {
if (mBidi == null) {
return Layout.DIR_LEFT_TO_RIGHT;
}
@@ -217,7 +219,7 @@
*/
public Directions getDirections(@IntRange(from = 0) int start, // inclusive
@IntRange(from = 0) int end) { // exclusive
- if (ClientFlags.icuBidiMigration()) {
+ if (Flags.icuBidiMigration()) {
// Easy case: mBidi == null means the text is all LTR and no bidi suppot is needed.
if (mBidi == null) {
return Layout.DIRS_ALL_LEFT_TO_RIGHT;
@@ -679,7 +681,7 @@
}
}
- if (ClientFlags.icuBidiMigration()) {
+ if (Flags.icuBidiMigration()) {
if ((textDir == TextDirectionHeuristics.LTR
|| textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
|| textDir == TextDirectionHeuristics.ANYRTL_LTR)
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 95460a3..cb49850 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -41,6 +41,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
+import com.android.text.flags.Flags;
import java.util.Arrays;
@@ -803,7 +804,7 @@
final int defaultAscent;
final int defaultDescent;
int defaultBottom;
- if (ClientFlags.fixLineHeightForLocale() && b.mMinimumFontMetrics != null) {
+ if (Flags.fixLineHeightForLocale() && b.mMinimumFontMetrics != null) {
defaultTop = (int) Math.floor(b.mMinimumFontMetrics.top);
defaultAscent = Math.round(b.mMinimumFontMetrics.ascent);
defaultDescent = Math.round(b.mMinimumFontMetrics.descent);
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 4dca284..9e02460 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -61,7 +61,6 @@
Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
Flags.FLAG_ICU_BIDI_MIGRATION,
Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU,
- Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS,
};
/**
@@ -76,7 +75,6 @@
Flags.fixLineHeightForLocale(),
Flags.icuBidiMigration(),
Flags.fixMisalignedContextMenu(),
- Flags.clearFontVariationSettings(),
};
/**
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 02c63db..155a3e4 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -240,3 +240,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "dont_break_email_in_nobreak_tag"
+ namespace: "text"
+ description: "Prevent line break inside email."
+ bug: "350691716"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 5c10db1..b796e0b 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -104,12 +104,23 @@
*/
public static final int FLAG_ANIMATE_RESIZING = 1 << 3;
+ /**
+ * Controls whether the {@link WindowInsets.Type#captionBar()} insets provided by this source
+ * should always be forcibly consumed. Unlike with {@link #FLAG_FORCE_CONSUMING}, when this
+ * flag is used the caption bar will be consumed even when the bar is requested to be visible.
+ *
+ * Note: this flag does not take effect when the window applies
+ * {@link WindowInsetsController.Appearance#APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND}.
+ */
+ public static final int FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR = 1 << 4;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = "FLAG_", value = {
FLAG_SUPPRESS_SCRIM,
FLAG_INSETS_ROUNDED_CORNER,
FLAG_FORCE_CONSUMING,
FLAG_ANIMATE_RESIZING,
+ FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR
})
public @interface Flags {}
@@ -555,6 +566,9 @@
if ((flags & FLAG_ANIMATE_RESIZING) != 0) {
joiner.add("ANIMATE_RESIZING");
}
+ if ((flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) {
+ joiner.add("FORCE_CONSUMING_OPAQUE_CAPTION_BAR");
+ }
return joiner.toString();
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index bbd9acf..6b4340a 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.util.SequenceUtils.getInitSeq;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
import static android.view.InsetsStateProto.DISPLAY_CUTOUT;
import static android.view.InsetsStateProto.DISPLAY_FRAME;
@@ -54,6 +55,7 @@
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.Objects;
@@ -131,18 +133,25 @@
final Rect relativeFrame = new Rect(frame);
final Rect relativeFrameMax = new Rect(frame);
@InsetsType int forceConsumingTypes = 0;
+ boolean forceConsumingOpaqueCaptionBar = false;
@InsetsType int suppressScrimTypes = 0;
final Rect[][] typeBoundingRectsMap = new Rect[Type.SIZE][];
final Rect[][] typeMaxBoundingRectsMap = new Rect[Type.SIZE][];
for (int i = mSources.size() - 1; i >= 0; i--) {
final InsetsSource source = mSources.valueAt(i);
final @InsetsType int type = source.getType();
+ final @InsetsSource.Flags int flags = source.getFlags();
- if ((source.getFlags() & InsetsSource.FLAG_FORCE_CONSUMING) != 0) {
+ if ((flags & InsetsSource.FLAG_FORCE_CONSUMING) != 0) {
forceConsumingTypes |= type;
}
- if ((source.getFlags() & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) {
+ if (Flags.enableCaptionCompatInsetForceConsumptionAlways()
+ && (flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) {
+ forceConsumingOpaqueCaptionBar = true;
+ }
+
+ if ((flags & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) {
suppressScrimTypes |= type;
}
@@ -177,7 +186,8 @@
}
return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
- forceConsumingTypes, suppressScrimTypes, calculateRelativeCutout(frame),
+ forceConsumingTypes, forceConsumingOpaqueCaptionBar, suppressScrimTypes,
+ calculateRelativeCutout(frame),
calculateRelativeRoundedCorners(frame),
calculateRelativePrivacyIndicatorBounds(frame),
calculateRelativeDisplayShape(frame),
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 987c8c8..e3ea6b22 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -97,6 +97,7 @@
private final int mFrameHeight;
private final @InsetsType int mForceConsumingTypes;
+ private final boolean mForceConsumingOpaqueCaptionBar;
private final @InsetsType int mSuppressScrimTypes;
private final boolean mSystemWindowInsetsConsumed;
private final boolean mStableInsetsConsumed;
@@ -123,7 +124,7 @@
static {
CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null),
- createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, 0, null,
+ createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, false, 0, null,
null, null, null, systemBars(), false, null, null, 0, 0);
}
@@ -144,6 +145,7 @@
boolean[] typeVisibilityMap,
boolean isRound,
@InsetsType int forceConsumingTypes,
+ boolean forceConsumingOpaqueCaptionBar,
@InsetsType int suppressScrimTypes,
DisplayCutout displayCutout,
RoundedCorners roundedCorners,
@@ -166,6 +168,7 @@
mTypeVisibilityMap = typeVisibilityMap;
mIsRound = isRound;
mForceConsumingTypes = forceConsumingTypes;
+ mForceConsumingOpaqueCaptionBar = forceConsumingOpaqueCaptionBar;
mSuppressScrimTypes = suppressScrimTypes;
mCompatInsetsTypes = compatInsetsTypes;
mCompatIgnoreVisibility = compatIgnoreVisibility;
@@ -196,7 +199,9 @@
this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap,
src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap,
src.mTypeVisibilityMap, src.mIsRound,
- src.mForceConsumingTypes, src.mSuppressScrimTypes,
+ src.mForceConsumingTypes,
+ src.mForceConsumingOpaqueCaptionBar,
+ src.mSuppressScrimTypes,
displayCutoutCopyConstructorArgument(src),
src.mRoundedCorners,
src.mPrivacyIndicatorBounds,
@@ -257,7 +262,7 @@
/** @hide */
@UnsupportedAppUsage
public WindowInsets(Rect systemWindowInsets) {
- this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, 0,
+ this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, false, 0,
null, null, null, null, systemBars(), false /* compatIgnoreVisibility */,
new Rect[SIZE][], null, 0, 0);
}
@@ -675,10 +680,10 @@
return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
mTypeVisibilityMap,
- mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
- null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
- mCompatInsetsTypes, mCompatIgnoreVisibility,
- mSystemWindowInsetsConsumed ? null : mTypeBoundingRectsMap,
+ mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+ mSuppressScrimTypes, null /* displayCutout */, mRoundedCorners,
+ mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes,
+ mCompatIgnoreVisibility, mSystemWindowInsetsConsumed ? null : mTypeBoundingRectsMap,
mStableInsetsConsumed ? null : mTypeMaxBoundingRectsMap,
mFrameWidth, mFrameHeight);
}
@@ -729,7 +734,8 @@
public WindowInsets consumeSystemWindowInsets() {
return new WindowInsets(null, null,
mTypeVisibilityMap,
- mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
+ mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+ mSuppressScrimTypes,
// If the system window insets types contain displayCutout, we should also consume
// it.
(mCompatInsetsTypes & displayCutout()) != 0
@@ -1024,6 +1030,13 @@
/**
* @hide
*/
+ public boolean isForceConsumingOpaqueCaptionBar() {
+ return mForceConsumingOpaqueCaptionBar;
+ }
+
+ /**
+ * @hide
+ */
public @InsetsType int getSuppressScrimTypes() {
return mSuppressScrimTypes;
}
@@ -1058,6 +1071,8 @@
result.append("\n ");
result.append("forceConsumingTypes=" + Type.toString(mForceConsumingTypes));
result.append("\n ");
+ result.append("forceConsumingOpaqueCaptionBar=" + mForceConsumingOpaqueCaptionBar);
+ result.append("\n ");
result.append("suppressScrimTypes=" + Type.toString(mSuppressScrimTypes));
result.append("\n ");
result.append("compatInsetsTypes=" + Type.toString(mCompatInsetsTypes));
@@ -1180,7 +1195,8 @@
? null
: insetInsets(mTypeMaxInsetsMap, left, top, right, bottom),
mTypeVisibilityMap,
- mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
+ mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+ mSuppressScrimTypes,
mDisplayCutoutConsumed
? null
: mDisplayCutout == null
@@ -1214,6 +1230,7 @@
return mIsRound == that.mIsRound
&& mForceConsumingTypes == that.mForceConsumingTypes
+ && mForceConsumingOpaqueCaptionBar == that.mForceConsumingOpaqueCaptionBar
&& mSuppressScrimTypes == that.mSuppressScrimTypes
&& mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed
&& mStableInsetsConsumed == that.mStableInsetsConsumed
@@ -1235,9 +1252,9 @@
public int hashCode() {
return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
- mForceConsumingTypes, mSuppressScrimTypes, mSystemWindowInsetsConsumed,
- mStableInsetsConsumed, mDisplayCutoutConsumed, mPrivacyIndicatorBounds,
- mDisplayShape, Arrays.deepHashCode(mTypeBoundingRectsMap),
+ mForceConsumingTypes, mForceConsumingOpaqueCaptionBar, mSuppressScrimTypes,
+ mSystemWindowInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed,
+ mPrivacyIndicatorBounds, mDisplayShape, Arrays.deepHashCode(mTypeBoundingRectsMap),
Arrays.deepHashCode(mTypeMaxBoundingRectsMap), mFrameWidth, mFrameHeight);
}
@@ -1367,6 +1384,7 @@
private boolean mIsRound;
private @InsetsType int mForceConsumingTypes;
+ private boolean mForceConsumingOpaqueCaptionBar;
private @InsetsType int mSuppressScrimTypes;
private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds();
@@ -1399,6 +1417,7 @@
mRoundedCorners = insets.mRoundedCorners;
mIsRound = insets.mIsRound;
mForceConsumingTypes = insets.mForceConsumingTypes;
+ mForceConsumingOpaqueCaptionBar = insets.mForceConsumingOpaqueCaptionBar;
mSuppressScrimTypes = insets.mSuppressScrimTypes;
mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
mDisplayShape = insets.mDisplayShape;
@@ -1687,6 +1706,13 @@
/** @hide */
@NonNull
+ public Builder setForceConsumingOpaqueCaptionBar(boolean forceConsumingOpaqueCaptionBar) {
+ mForceConsumingOpaqueCaptionBar = forceConsumingOpaqueCaptionBar;
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
public Builder setSuppressScrimTypes(@InsetsType int suppressScrimTypes) {
mSuppressScrimTypes = suppressScrimTypes;
return this;
@@ -1765,9 +1791,9 @@
public WindowInsets build() {
return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
- mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutout,
- mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, systemBars(),
- false /* compatIgnoreVisibility */,
+ mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+ mSuppressScrimTypes, mDisplayCutout, mRoundedCorners, mPrivacyIndicatorBounds,
+ mDisplayShape, systemBars(), false /* compatIgnoreVisibility */,
mSystemInsetsConsumed ? null : mTypeBoundingRectsMap,
mStableInsetsConsumed ? null : mTypeMaxBoundingRectsMap,
mFrameWidth, mFrameHeight);
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index f3cde43..376e66f 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -19,6 +19,7 @@
import static android.view.accessibility.AccessibilityNodeInfo.FOCUS_ACCESSIBILITY;
+import android.annotation.Nullable;
import android.os.Build;
import android.os.SystemClock;
import android.util.ArraySet;
@@ -48,6 +49,8 @@
private boolean mEnabled = true;
+ private final SparseArray<String> mWindowIdToEventSourceClassName = new SparseArray<>();
+
/**
* {@link AccessibilityEvent} types that are critical for the cache to stay up to date
*
@@ -273,8 +276,11 @@
clearSubTreeLocked(event.getWindowId(), event.getSourceNodeId());
} break;
- case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
+ case AccessibilityEvent.TYPE_WINDOWS_CHANGED: {
mValidWindowCacheTimeStamp = event.getEventTime();
+ if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_REMOVED) {
+ mWindowIdToEventSourceClassName.remove(event.getWindowId());
+ }
if (event.getWindowChanges()
== AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) {
// Don't need to clear all cache. Unless the changes are related to
@@ -282,8 +288,15 @@
clearWindowCacheLocked();
break;
}
+ clear();
+ }
+ break;
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
mValidWindowCacheTimeStamp = event.getEventTime();
+ if (event.getContentChangeTypes() == 0 && event.getClassName() != null) {
+ mWindowIdToEventSourceClassName.put(event.getWindowId(),
+ event.getClassName().toString());
+ }
clear();
} break;
}
@@ -907,6 +920,12 @@
}
}
+ /** Returns the source class associated with the window with the given id. */
+ @Nullable
+ public String getEventSourceClassName(int windowId) {
+ return mWindowIdToEventSourceClassName.get(windowId);
+ }
+
// Layer of indirection included to break dependency chain for testing
public static class AccessibilityNodeRefresher {
/** Refresh the given AccessibilityNodeInfo object. */
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 90cfcb1..a5ba294 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -982,7 +982,6 @@
private long mParentNodeId = UNDEFINED_NODE_ID;
private long mLabelForId = UNDEFINED_NODE_ID;
private long mLabeledById = UNDEFINED_NODE_ID;
- private LongArray mLabeledByIds;
private long mTraversalBefore = UNDEFINED_NODE_ID;
private long mTraversalAfter = UNDEFINED_NODE_ID;
@@ -3600,131 +3599,6 @@
}
/**
- * Adds the view which serves as the label of the view represented by
- * this info for accessibility purposes. When more than one labels are
- * added, the content from each label is combined in the order that
- * they are added.
- * <p>
- * If visible text can be used to describe or give meaning to this UI,
- * this method is preferred. For example, a TextView before an EditText
- * in the UI usually specifies what information is contained in the
- * EditText. Hence, the EditText is labelled by the TextView.
- * </p>
- *
- * @param label A view that labels this node's source.
- */
- @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
- public void addLabeledBy(@NonNull View label) {
- addLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
- }
-
- /**
- * Adds the view which serves as the label of the view represented by
- * this info for accessibility purposes. If <code>virtualDescendantId</code>
- * is {@link View#NO_ID} the root is set as the label. When more than one
- * labels are added, the content from each label is combined in the order
- * that they are added.
- * <p>
- * A virtual descendant is an imaginary View that is reported as a part of the view
- * hierarchy for accessibility purposes. This enables custom views that draw complex
- * content to report themselves as a tree of virtual views, thus conveying their
- * logical structure.
- * </p>
- * <p>
- * If visible text can be used to describe or give meaning to this UI,
- * this method is preferred. For example, a TextView before an EditText
- * in the UI usually specifies what information is contained in the
- * EditText. Hence, the EditText is labelled by the TextView.
- * </p>
- * <p>
- * <strong>Note:</strong> Cannot be called from an
- * {@link android.accessibilityservice.AccessibilityService}.
- * This class is made immutable before being delivered to an AccessibilityService.
- * </p>
- *
- * @param root A root whose virtual descendant labels this node's source.
- * @param virtualDescendantId The id of the virtual descendant.
- */
- @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
- public void addLabeledBy(@NonNull View root, int virtualDescendantId) {
- enforceNotSealed();
- Preconditions.checkNotNull(root, "%s must not be null", root);
- if (mLabeledByIds == null) {
- mLabeledByIds = new LongArray();
- }
- mLabeledById = makeNodeId(root.getAccessibilityViewId(), virtualDescendantId);
- mLabeledByIds.add(mLabeledById);
- }
-
- /**
- * Gets the list of node infos which serve as the labels of the view represented by
- * this info for accessibility purposes.
- *
- * @return The list of labels in the order that they were added.
- */
- @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
- public @NonNull List<AccessibilityNodeInfo> getLabeledByList() {
- enforceSealed();
- List<AccessibilityNodeInfo> labels = new ArrayList<>();
- if (mLabeledByIds == null) {
- return labels;
- }
- for (int i = 0; i < mLabeledByIds.size(); i++) {
- labels.add(getNodeForAccessibilityId(mConnectionId, mWindowId, mLabeledByIds.get(i)));
- }
- return labels;
- }
-
- /**
- * Removes a label. If the label was not previously added to the node,
- * calling this method has no effect.
- * <p>
- * <strong>Note:</strong> Cannot be called from an
- * {@link android.accessibilityservice.AccessibilityService}.
- * This class is made immutable before being delivered to an AccessibilityService.
- * </p>
- *
- * @param label The node which serves as this node's label.
- * @return true if the label was present
- * @see #addLabeledBy(View)
- */
- @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
- public boolean removeLabeledBy(@NonNull View label) {
- return removeLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
- }
-
- /**
- * Removes a virtual label which is a descendant of the given
- * <code>root</code>. If the label was not previously added to the node,
- * calling this method has no effect.
- *
- * @param root The root of the virtual subtree.
- * @param virtualDescendantId The id of the virtual node which serves as this node's label.
- * @return true if the label was present
- * @see #addLabeledBy(View, int)
- */
- @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
- public boolean removeLabeledBy(@NonNull View root, int virtualDescendantId) {
- enforceNotSealed();
- final LongArray labeledByIds = mLabeledByIds;
- if (labeledByIds == null) {
- return false;
- }
- final int rootAccessibilityViewId =
- (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
- final long labeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
- if (mLabeledById == labeledById) {
- mLabeledById = UNDEFINED_NODE_ID;
- }
- final int index = labeledByIds.indexOf(labeledById);
- if (index < 0) {
- return false;
- }
- labeledByIds.remove(index);
- return true;
- }
-
- /**
* Sets the view which serves as the label of the view represented by
* this info for accessibility purposes.
*
@@ -3757,17 +3631,7 @@
enforceNotSealed();
final int rootAccessibilityViewId = (root != null)
? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
- if (Flags.supportMultipleLabeledby()) {
- if (mLabeledByIds == null) {
- mLabeledByIds = new LongArray();
- } else {
- mLabeledByIds.clear();
- }
- }
mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
- if (Flags.supportMultipleLabeledby()) {
- mLabeledByIds.add(mLabeledById);
- }
}
/**
@@ -4378,12 +4242,6 @@
fieldIndex++;
if (mLabeledById != DEFAULT.mLabeledById) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
- if (Flags.supportMultipleLabeledby()) {
- if (!LongArray.elementsEqual(mLabeledByIds, DEFAULT.mLabeledByIds)) {
- nonDefaultFields |= bitAt(fieldIndex);
- }
- fieldIndex++;
- }
if (mTraversalBefore != DEFAULT.mTraversalBefore) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
@@ -4525,20 +4383,6 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mParentNodeId);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabelForId);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById);
- if (Flags.supportMultipleLabeledby()) {
- if (isBitSet(nonDefaultFields, fieldIndex++)) {
- final LongArray labeledByIds = mLabeledByIds;
- if (labeledByIds == null) {
- parcel.writeInt(0);
- } else {
- final int labeledByIdsSize = labeledByIds.size();
- parcel.writeInt(labeledByIdsSize);
- for (int i = 0; i < labeledByIdsSize; i++) {
- parcel.writeLong(labeledByIds.get(i));
- }
- }
- }
- }
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -4706,9 +4550,6 @@
mParentNodeId = other.mParentNodeId;
mLabelForId = other.mLabelForId;
mLabeledById = other.mLabeledById;
- if (Flags.supportMultipleLabeledby()) {
- mLabeledByIds = other.mLabeledByIds;
- }
mTraversalBefore = other.mTraversalBefore;
mTraversalAfter = other.mTraversalAfter;
mMinDurationBetweenContentChanges = other.mMinDurationBetweenContentChanges;
@@ -4815,20 +4656,6 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) mParentNodeId = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mLabelForId = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong();
- if (Flags.supportMultipleLabeledby()) {
- if (isBitSet(nonDefaultFields, fieldIndex++)) {
- final int labeledByIdsSize = parcel.readInt();
- if (labeledByIdsSize <= 0) {
- mLabeledByIds = null;
- } else {
- mLabeledByIds = new LongArray(labeledByIdsSize);
- for (int i = 0; i < labeledByIdsSize; i++) {
- final long labeledById = parcel.readLong();
- mLabeledByIds.add(labeledById);
- }
- }
- }
- }
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index d40eeda..8b6ead7 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -76,7 +76,6 @@
import android.text.Spanned;
import android.text.SpannedString;
import android.text.StaticLayout;
-import android.text.TextFlags;
import android.text.TextUtils;
import android.text.method.InsertModeTransformationMethod;
import android.text.method.KeyListener;
@@ -471,7 +470,6 @@
private static final int LINE_CHANGE_SLOP_MIN_DP = 8;
private int mLineChangeSlopMax;
private int mLineChangeSlopMin;
- private boolean mUseNewContextMenu;
private final AccessibilitySmartActions mA11ySmartActions;
private InsertModeController mInsertModeController;
@@ -502,9 +500,6 @@
mLineSlopRatio = AppGlobals.getFloatCoreSetting(
WidgetFlags.KEY_LINE_SLOP_RATIO,
WidgetFlags.LINE_SLOP_RATIO_DEFAULT);
- mUseNewContextMenu = AppGlobals.getIntCoreSetting(
- TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
- TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
if (TextView.DEBUG_CURSOR) {
logCursor("Editor", "Cursor drag from anywhere is %s.",
mFlagCursorDragFromAnywhereEnabled ? "enabled" : "disabled");
@@ -3216,29 +3211,18 @@
final int menuItemOrderCut = 4;
final int menuItemOrderCopy = 5;
final int menuItemOrderPaste = 6;
- final int menuItemOrderPasteAsPlainText;
- final int menuItemOrderSelectAll;
- final int menuItemOrderShare;
- final int menuItemOrderAutofill;
- if (mUseNewContextMenu) {
- menuItemOrderPasteAsPlainText = 7;
- menuItemOrderSelectAll = 8;
- menuItemOrderShare = 9;
- menuItemOrderAutofill = 10;
+ final int menuItemOrderPasteAsPlainText = 7;
+ final int menuItemOrderSelectAll = 8;
+ final int menuItemOrderShare = 9;
+ final int menuItemOrderAutofill = 10;
- menu.setOptionalIconsVisible(true);
- menu.setGroupDividerEnabled(true);
+ menu.setOptionalIconsVisible(true);
+ menu.setGroupDividerEnabled(true);
- setAssistContextMenuItems(menu);
+ setAssistContextMenuItems(menu);
- final int keyboard = mTextView.getResources().getConfiguration().keyboard;
- menu.setQwertyMode(keyboard == Configuration.KEYBOARD_QWERTY);
- } else {
- menuItemOrderShare = 7;
- menuItemOrderSelectAll = 8;
- menuItemOrderAutofill = 10;
- menuItemOrderPasteAsPlainText = 11;
- }
+ final int keyboard = mTextView.getResources().getConfiguration().keyboard;
+ menu.setQwertyMode(keyboard == Configuration.KEYBOARD_QWERTY);
final TypedArray a = mTextView.getContext().obtainStyledAttributes(new int[] {
// TODO: Make Undo/Redo be public attribute.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f7e0ec8..9099db8 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -105,7 +105,6 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.text.BoringLayout;
-import android.text.ClientFlags;
import android.text.DynamicLayout;
import android.text.Editable;
import android.text.GetChars;
@@ -1659,7 +1658,7 @@
if (!hasUseBoundForWidthValue) {
if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) {
- mUseBoundsForWidth = ClientFlags.useBoundsForWidth();
+ mUseBoundsForWidth = Flags.useBoundsForWidth();
} else {
mUseBoundsForWidth = false;
}
@@ -9732,7 +9731,7 @@
break;
case KeyEvent.KEYCODE_ESCAPE:
- if (com.android.text.flags.Flags.escapeClearsFocus() && event.hasNoModifiers()) {
+ if (Flags.escapeClearsFocus() && event.hasNoModifiers()) {
if (mEditor != null && mEditor.getTextActionMode() != null) {
stopTextActionMode();
return KEY_EVENT_HANDLED;
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/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index ec4e3e9..3cfde87 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -30,6 +30,8 @@
import android.os.Parcelable;
import android.view.WindowManager;
+import com.android.window.flags.Flags;
+
/**
* A parcelable filter that can be used for rerouting transitions to a remote. This is a local
* representation so that the transition system doesn't need to make blocking queries over
@@ -183,6 +185,9 @@
public ComponentName mTopActivity;
public IBinder mLaunchCookie;
+ /** If non-null, requires the change to specifically have or not-have a custom animation. */
+ public Boolean mCustomAnimation = null;
+
public Requirement() {
}
@@ -196,6 +201,9 @@
mOrder = in.readInt();
mTopActivity = in.readTypedObject(ComponentName.CREATOR);
mLaunchCookie = in.readStrongBinder();
+ // 0: null, 1: false, 2: true
+ final int customAnimRaw = in.readInt();
+ mCustomAnimation = customAnimRaw == 0 ? null : Boolean.valueOf(customAnimRaw == 2);
}
/** Go through changes and find if at-least one change matches this filter */
@@ -237,6 +245,23 @@
if (!matchesCookie(change.getTaskInfo())) {
continue;
}
+ if (mCustomAnimation != null
+ // only applies to activity/task
+ && (change.getTaskInfo() != null
+ || change.getActivityComponent() != null)) {
+ final TransitionInfo.AnimationOptions opts =
+ Flags.moveAnimationOptionsToChange() ? change.getAnimationOptions()
+ : info.getAnimationOptions();
+ if (opts != null) {
+ boolean canActuallyOverride = change.getTaskInfo() == null
+ || opts.getOverrideTaskTransition();
+ if (mCustomAnimation != canActuallyOverride) {
+ continue;
+ }
+ } else if (mCustomAnimation) {
+ continue;
+ }
+ }
return true;
}
return false;
@@ -286,6 +311,8 @@
dest.writeInt(mOrder);
dest.writeTypedObject(mTopActivity, flags);
dest.writeStrongBinder(mLaunchCookie);
+ int customAnimRaw = mCustomAnimation == null ? 0 : (mCustomAnimation ? 2 : 1);
+ dest.writeInt(customAnimRaw);
}
@NonNull
@@ -327,6 +354,9 @@
out.append(" order=" + containerOrderToString(mOrder));
out.append(" topActivity=").append(mTopActivity);
out.append(" launchCookie=").append(mLaunchCookie);
+ if (mCustomAnimation != null) {
+ out.append(" customAnim=").append(mCustomAnimation.booleanValue());
+ }
out.append("}");
return out.toString();
}
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/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index f8a2a31..4230641 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -123,17 +123,6 @@
flag {
namespace: "windowing_sdk"
- name: "disable_object_pool"
- description: "Whether to disable object pool and let the GC handle lifecycle items"
- bug: "311089192"
- is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- namespace: "windowing_sdk"
name: "rear_display_disable_force_desktop_system_decorations"
description: "Block system decorations from being added to a rear display when desktop mode is forced"
bug: "346103150"
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/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 87e22ed..4828393 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -29,6 +29,7 @@
import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
@@ -36,6 +37,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.flags.Flags.customizableWindowHeaders;
import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL;
@@ -226,6 +228,7 @@
private boolean mLastHasLeftStableInset = false;
private int mLastWindowFlags = 0;
private @InsetsType int mLastForceConsumingTypes = 0;
+ private boolean mLastForceConsumingOpaqueCaptionBar = false;
private @InsetsType int mLastSuppressScrimTypes = 0;
private int mRootScrollY = 0;
@@ -1068,8 +1071,12 @@
WindowManager.LayoutParams attrs = mWindow.getAttributes();
int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
+ final ViewRootImpl viewRoot = getViewRootImpl();
final WindowInsetsController controller = getWindowInsetsController();
final @InsetsType int requestedVisibleTypes = controller.getRequestedVisibleTypes();
+ final @Appearance int appearance = viewRoot != null
+ ? viewRoot.mWindowAttributes.insetsFlags.appearance
+ : controller.getSystemBarsAppearance();
// IME is an exceptional floating window that requires color view.
final boolean isImeWindow =
@@ -1080,13 +1087,9 @@
& FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
mLastWindowFlags = attrs.flags;
- final ViewRootImpl viewRoot = getViewRootImpl();
- final @Appearance int appearance = viewRoot != null
- ? viewRoot.mWindowAttributes.insetsFlags.appearance
- : controller.getSystemBarsAppearance();
-
if (insets != null) {
mLastForceConsumingTypes = insets.getForceConsumingTypes();
+ mLastForceConsumingOpaqueCaptionBar = insets.isForceConsumingOpaqueCaptionBar();
final boolean clearsCompatInsets = clearsCompatInsets(attrs.type, attrs.flags,
getResources().getConfiguration().windowConfiguration.getActivityType(),
@@ -1209,16 +1212,20 @@
final boolean hideCaptionBar = fullscreen
|| (requestedVisibleTypes & WindowInsets.Type.captionBar()) == 0;
- final boolean consumingCaptionBar =
- ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0
+ final boolean consumingCaptionBar = Flags.enableCaptionCompatInsetForceConsumption()
+ && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0
&& hideCaptionBar);
- final int consumedTop;
- if (Flags.enableCaptionCompatInsetForceConsumption()) {
- consumedTop = (consumingStatusBar || consumingCaptionBar) ? mLastTopInset : 0;
- } else {
- consumedTop = consumingStatusBar ? mLastTopInset : 0;
- }
+ final boolean isOpaqueCaptionBar = customizableWindowHeaders()
+ && (appearance & APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) == 0;
+ final boolean consumingOpaqueCaptionBar =
+ Flags.enableCaptionCompatInsetForceConsumptionAlways()
+ && mLastForceConsumingOpaqueCaptionBar
+ && isOpaqueCaptionBar;
+
+ final int consumedTop =
+ (consumingStatusBar || consumingCaptionBar || consumingOpaqueCaptionBar)
+ ? mLastTopInset : 0;
int consumedRight = consumingNavBar ? mLastRightInset : 0;
int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 42be4fc..ec004d0 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2501,6 +2501,9 @@
if (mEdgeToEdgeEnforced) {
getAttributes().privateFlags |= PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
mDecorFitsSystemWindows = false;
+ mStatusBarColor = Color.TRANSPARENT;
+ mNavigationBarDividerColor = Color.TRANSPARENT;
+ // mNavigationBarColor is not reset here because it might be used to draw the scrim.
}
if (CompatChanges.isChangeEnabled(OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE)
&& !a.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement,
diff --git a/core/java/com/android/internal/policy/SystemBarUtils.java b/core/java/com/android/internal/policy/SystemBarUtils.java
index efa3697..4ed15fa 100644
--- a/core/java/com/android/internal/policy/SystemBarUtils.java
+++ b/core/java/com/android/internal/policy/SystemBarUtils.java
@@ -92,4 +92,11 @@
// Equals to status bar height if status bar height is bigger.
return Math.max(defaultSize, statusBarHeight);
}
+
+ /**
+ * Gets the taskbar frame height.
+ */
+ public static int getTaskbarHeight(Resources res) {
+ return res.getDimensionPixelSize(R.dimen.taskbar_frame_height);
+ }
}
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 792c223..f177e14 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -82,4 +82,5 @@
void onCallBackModeStopped(int type, int reason);
void onSimultaneousCallingStateChanged(in int[] subIds);
void onCarrierRoamingNtnModeChanged(in boolean active);
+ void onCarrierRoamingNtnEligibleStateChanged(in boolean eligible);
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 04332cd..e500a37a 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -121,4 +121,5 @@
void notifyCallbackModeStarted(int phoneId, int subId, int type);
void notifyCallbackModeStopped(int phoneId, int subId, int type, int reason);
void notifyCarrierRoamingNtnModeChanged(int subId, in boolean active);
+ void notifyCarrierRoamingNtnEligibleStateChanged(int subId, in boolean eligible);
}
diff --git a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
index f08e860..c24cf5f 100644
--- a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
@@ -5,14 +5,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
-import android.app.AppGlobals;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Parcelable;
import android.os.SystemClock;
-import android.text.TextFlags;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -217,11 +215,7 @@
mSubMenuHoverHandler = new Handler();
- mItemLayout = AppGlobals.getIntCoreSetting(
- TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
- TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0
- ? com.android.internal.R.layout.cascading_menu_item_layout_material
- : com.android.internal.R.layout.cascading_menu_item_layout;
+ mItemLayout = com.android.internal.R.layout.cascading_menu_item_layout_material;
}
@Override
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 468705d..b73cacb 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -16,13 +16,10 @@
package com.android.internal.view.menu;
-import android.app.AppGlobals;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.text.ClientFlags;
-import android.text.TextFlags;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -36,6 +33,8 @@
import android.widget.RadioButton;
import android.widget.TextView;
+import com.android.text.flags.Flags;
+
/**
* The item view for each item in the ListView-based MenuViews.
*/
@@ -62,8 +61,6 @@
private int mMenuType;
- private boolean mUseNewContextMenu;
-
private LayoutInflater mInflater;
private boolean mForceShowIcon;
@@ -90,10 +87,6 @@
a.recycle();
b.recycle();
-
- mUseNewContextMenu = AppGlobals.getIntCoreSetting(
- TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
- TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
}
public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -291,7 +284,7 @@
private void insertIconView() {
LayoutInflater inflater = getInflater();
mIconView = (ImageView) inflater.inflate(
- mUseNewContextMenu && !ClientFlags.fixMisalignedContextMenu()
+ !Flags.fixMisalignedContextMenu()
? com.android.internal.R.layout.list_menu_item_fixed_size_icon :
com.android.internal.R.layout.list_menu_item_icon,
this, false);
diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
index c254e99..c43a8c6 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -16,12 +16,9 @@
package com.android.internal.view.menu;
-import android.app.AppGlobals;
import android.content.Context;
import android.content.res.Resources;
import android.os.Parcelable;
-import android.text.ClientFlags;
-import android.text.TextFlags;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -38,6 +35,8 @@
import android.widget.PopupWindow.OnDismissListener;
import android.widget.TextView;
+import com.android.text.flags.Flags;
+
import java.util.Objects;
/**
@@ -120,16 +119,11 @@
public StandardMenuPopup(Context context, MenuBuilder menu, View anchorView, int popupStyleAttr,
int popupStyleRes, boolean overflowOnly) {
mContext = Objects.requireNonNull(context);
- boolean useNewContextMenu = AppGlobals.getIntCoreSetting(
- TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
- TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
-
mMenu = menu;
mOverflowOnly = overflowOnly;
final LayoutInflater inflater = LayoutInflater.from(context);
mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly,
- ClientFlags.fixMisalignedContextMenu() && useNewContextMenu
- ? ITEM_LAYOUT_MATERIAL : ITEM_LAYOUT);
+ Flags.fixMisalignedContextMenu() ? ITEM_LAYOUT_MATERIAL : ITEM_LAYOUT);
mPopupStyleAttr = popupStyleAttr;
mPopupStyleRes = popupStyleRes;
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 809ec63..e5ac0e1 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -33,6 +33,7 @@
#include <algorithm>
#include <array>
+#include <cstring>
#include <limits>
#include <memory>
#include <string>
@@ -50,7 +51,6 @@
#include <inttypes.h>
#include <pwd.h>
#include <signal.h>
-#include <string.h>
#include <sys/epoll.h>
#include <sys/errno.h>
#include <sys/pidfd.h>
@@ -73,13 +73,13 @@
// readProcFile() are reading files under this threshold, e.g.,
// /proc/pid/stat. /proc/pid/time_in_state ends up being about 520
// bytes, so use 1024 for the stack to provide a bit of slack.
-static constexpr ssize_t kProcReadStackBufferSize = 1024;
+static constexpr size_t kProcReadStackBufferSize = 1024;
// The other files we read from proc tend to be a bit larger (e.g.,
// /proc/stat is about 3kB), so once we exhaust the stack buffer,
// retry with a relatively large heap-allocated buffer. We double
// this size and retry until the whole file fits.
-static constexpr ssize_t kProcReadMinHeapBufferSize = 4096;
+static constexpr size_t kProcReadMinHeapBufferSize = 4096;
#if GUARD_THREAD_PRIORITY
Mutex gKeyCreateMutex;
@@ -817,7 +817,6 @@
}
DIR* dirp = opendir(file8);
-
env->ReleaseStringUTFChars(file, file8);
if(dirp == NULL) {
@@ -850,6 +849,7 @@
jintArray newArray = env->NewIntArray(newCount);
if (newArray == NULL) {
closedir(dirp);
+ if (curData) env->ReleaseIntArrayElements(lastArray, curData, 0);
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return NULL;
}
@@ -1046,78 +1046,71 @@
return JNI_FALSE;
}
- const char* file8 = env->GetStringUTFChars(file, NULL);
- if (file8 == NULL) {
+ auto releaser = [&](const char* jniStr) { env->ReleaseStringUTFChars(file, jniStr); };
+ std::unique_ptr<const char[], decltype(releaser)> file8(env->GetStringUTFChars(file, NULL),
+ releaser);
+ if (!file8) {
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return JNI_FALSE;
}
- ::android::base::unique_fd fd(open(file8, O_RDONLY | O_CLOEXEC));
+ ::android::base::unique_fd fd(open(file8.get(), O_RDONLY | O_CLOEXEC));
if (!fd.ok()) {
if (kDebugProc) {
- ALOGW("Unable to open process file: %s\n", file8);
+ ALOGW("Unable to open process file: %s\n", file8.get());
}
- env->ReleaseStringUTFChars(file, file8);
return JNI_FALSE;
}
- env->ReleaseStringUTFChars(file, file8);
// Most proc files we read are small, so we go through the loop
- // with the stack buffer firstly. We allocate a buffer big
- // enough for the whole file.
+ // with the stack buffer first. We allocate a buffer big enough
+ // for most files.
- char readBufferStack[kProcReadStackBufferSize];
- std::unique_ptr<char[]> readBufferHeap;
- char* readBuffer = &readBufferStack[0];
- ssize_t readBufferSize = kProcReadStackBufferSize;
- ssize_t numberBytesRead;
+ char stackBuf[kProcReadStackBufferSize];
+ std::vector<char> heapBuf;
+ char* buf = stackBuf;
+
+ size_t remaining = sizeof(stackBuf);
off_t offset = 0;
- for (;;) {
- ssize_t requestedBufferSize = readBufferSize - offset;
- // By using pread, we can avoid an lseek to rewind the FD
- // before retry, saving a system call.
- numberBytesRead =
- TEMP_FAILURE_RETRY(pread(fd, readBuffer + offset, requestedBufferSize, offset));
- if (numberBytesRead < 0) {
+ ssize_t numBytesRead;
+
+ do {
+ numBytesRead = TEMP_FAILURE_RETRY(pread(fd, buf + offset, remaining, offset));
+ if (numBytesRead < 0) {
if (kDebugProc) {
ALOGW("Unable to read process file err: %s file: %s fd=%d\n",
- strerror_r(errno, &readBufferStack[0], sizeof(readBufferStack)), file8,
- fd.get());
+ strerror_r(errno, stackBuf, sizeof(stackBuf)), file8.get(), fd.get());
}
return JNI_FALSE;
}
- if (numberBytesRead == 0) {
- // End of file.
- numberBytesRead = offset;
- break;
- }
- if (numberBytesRead < requestedBufferSize) {
- // Read less bytes than requested, it's not an error per pread(2).
- offset += numberBytesRead;
- } else {
- // Buffer is fully used, try to grow it.
- if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
- if (kDebugProc) {
- ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+
+ offset += numBytesRead;
+ remaining -= numBytesRead;
+
+ if (numBytesRead && !remaining) {
+ if (buf == stackBuf) {
+ heapBuf.resize(kProcReadMinHeapBufferSize);
+ static_assert(kProcReadMinHeapBufferSize > sizeof(stackBuf));
+ std::memcpy(heapBuf.data(), stackBuf, sizeof(stackBuf));
+ } else {
+ constexpr size_t MAX_READABLE_PROCFILE_SIZE = 64 << 20;
+ if (heapBuf.size() >= MAX_READABLE_PROCFILE_SIZE) {
+ if (kDebugProc) {
+ ALOGW("Proc file too big: %s fd=%d size=%zu\n",
+ file8.get(), fd.get(), heapBuf.size());
+ }
+ return JNI_FALSE;
}
- return JNI_FALSE;
+ heapBuf.resize(2 * heapBuf.size());
}
- readBufferSize = std::max(readBufferSize * 2, kProcReadMinHeapBufferSize);
- readBufferHeap.reset(); // Free address space before getting more.
- readBufferHeap = std::make_unique<char[]>(readBufferSize);
- if (!readBufferHeap) {
- jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
- return JNI_FALSE;
- }
- readBuffer = readBufferHeap.get();
- offset = 0;
+ buf = heapBuf.data();
+ remaining = heapBuf.size() - offset;
}
- }
+ } while (numBytesRead != 0);
// parseProcLineArray below modifies the buffer while parsing!
return android_os_Process_parseProcLineArray(
- env, clazz, readBuffer, 0, numberBytesRead,
- format, outStrings, outLongs, outFloats);
+ env, clazz, buf, 0, offset, format, outStrings, outLongs, outFloats);
}
void android_os_Process_setApplicationObject(JNIEnv* env, jobject clazz,
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/res/res/values/config.xml b/core/res/res/values/config.xml
index 8cb7646..2afc303 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7100,4 +7100,8 @@
<!-- Whether to enable the private space search illustration and search tile content in "Hide Private Space" settings page.
OEM/Partner can explicitly opt to hide the illustration and search tile content. -->
<bool name="config_enableSearchTileHideIllustrationInPrivateSpace">true</bool>
+
+ <!-- The maximum number of call log entries for each sim card that can be stored in the call log
+ provider on the device. -->
+ <integer name="config_maximumCallLogEntriesPerSim">500</integer>
</resources>
diff --git a/core/res/res/values/config_device_idle.xml b/core/res/res/values/config_device_idle.xml
index 7a707c0..cc7b891 100644
--- a/core/res/res/values/config_device_idle.xml
+++ b/core/res/res/values/config_device_idle.xml
@@ -28,7 +28,7 @@
<integer name="device_idle_flex_time_short_ms">60000</integer>
<!-- Default for DeviceIdleController.Constants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT -->
- <integer name="device_idle_light_after_inactive_to_ms">60000</integer>
+ <integer name="device_idle_light_after_inactive_to_ms">240000</integer>
<!-- Default for DeviceIdleController.Constants.LIGHT_IDLE_TIMEOUT -->
<integer name="device_idle_light_idle_to_ms">300000</integer>
@@ -67,10 +67,10 @@
<integer name="device_idle_min_deep_maintenance_time_ms">30000</integer>
<!-- Default for DeviceIdleController.Constants.INACTIVE_TIMEOUT -->
- <integer name="device_idle_inactive_to_ms">60000</integer>
+ <integer name="device_idle_inactive_to_ms">15000</integer>
<!-- Default for DeviceIdleController.Constants.SENSING_TIMEOUT -->
- <integer name="device_idle_sensing_to_ms">30000</integer>
+ <integer name="device_idle_sensing_to_ms">15000</integer>
<!-- Default for DeviceIdleController.Constants.LOCATING_TIMEOUT -->
<integer name="device_idle_locating_to_ms">15000</integer>
@@ -79,13 +79,13 @@
<item name="device_idle_location_accuracy" format="float" type="integer">20.0</item>
<!-- Default for DeviceIdleController.Constants.MOTION_INACTIVE_TIMEOUT -->
- <integer name="device_idle_motion_inactive_to_ms">600000</integer>
+ <integer name="device_idle_motion_inactive_to_ms">30000</integer>
<!-- Default for DeviceIdleController.Constants.MOTION_INACTIVE_TIMEOUT_FLEX -->
<integer name="device_idle_motion_inactive_to_flex_ms">60000</integer>
<!-- Default for DeviceIdleController.Constants.IDLE_AFTER_INACTIVE_TIMEOUT -->
- <integer name="device_idle_idle_after_inactive_to_ms">60000</integer>
+ <integer name="device_idle_idle_after_inactive_to_ms">15000</integer>
<!-- Default for DeviceIdleController.Constants.IDLE_PENDING_TIMEOUT -->
<integer name="device_idle_idle_pending_to_ms">300000</integer>
@@ -100,7 +100,7 @@
<integer name="device_idle_quick_doze_delay_to_ms">60000</integer>
<!-- Default for DeviceIdleController.Constants.IDLE_TIMEOUT -->
- <integer name="device_idle_idle_to_ms">900000</integer>
+ <integer name="device_idle_idle_to_ms">3600000</integer>
<!-- Default for DeviceIdleController.Constants.MAX_IDLE_TIMEOUT -->
<integer name="device_idle_max_idle_to_ms">21600000</integer>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4a425cb..bc8c778 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5530,6 +5530,8 @@
<java-symbol type="integer" name="config_wallpaperFrameRateCompatibility" />
<java-symbol type="integer" name="config_defaultMinEmergencyGestureTapDurationMillis" />
+ <java-symbol type="integer" name="config_maximumCallLogEntriesPerSim" />
+
<!-- Back swipe thresholds -->
<java-symbol type="dimen" name="navigation_edge_action_progress_threshold" />
<java-symbol type="dimen" name="back_progress_non_linear_factor" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index c51c3a5..24f6cea 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -254,8 +254,8 @@
try {
// Send process level config change.
ClientTransaction transaction = newTransaction(activityThread);
- transaction.addTransactionItem(ConfigurationChangeItem.obtain(
- newConfig, DEVICE_ID_INVALID));
+ transaction.addTransactionItem(
+ new ConfigurationChangeItem(newConfig, DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -474,15 +474,15 @@
activity.mTestLatch = new CountDownLatch(1);
ClientTransaction transaction = newTransaction(activityThread);
- transaction.addTransactionItem(ConfigurationChangeItem.obtain(
- processConfigLandscape, DEVICE_ID_INVALID));
+ transaction.addTransactionItem(
+ new ConfigurationChangeItem(processConfigLandscape, DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
transaction = newTransaction(activityThread);
transaction.addTransactionItem(new ActivityConfigurationChangeItem(
activity.getActivityToken(), activityConfigLandscape, new ActivityWindowInfo()));
- transaction.addTransactionItem(ConfigurationChangeItem.obtain(
- processConfigPortrait, DEVICE_ID_INVALID));
+ transaction.addTransactionItem(
+ new ConfigurationChangeItem(processConfigPortrait, DEVICE_ID_INVALID));
transaction.addTransactionItem(new ActivityConfigurationChangeItem(
activity.getActivityToken(), activityConfigPortrait, new ActivityWindowInfo()));
appThread.scheduleTransaction(transaction);
@@ -847,7 +847,7 @@
final ActivityWindowInfo newInfo = new ActivityWindowInfo();
newInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
new Rect(0, 0, 1000, 1000));
- final ActivityRelaunchItem relaunchItem = ActivityRelaunchItem.obtain(
+ final ActivityRelaunchItem relaunchItem = new ActivityRelaunchItem(
activity.getActivityToken(), null, null, 0,
new MergedConfiguration(currentConfig, currentConfig),
false /* preserveWindow */, newInfo);
@@ -978,7 +978,7 @@
} else {
activityWindowInfo = record.getActivityWindowInfo();
}
- final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(
+ final ClientTransactionItem callbackItem = new ActivityRelaunchItem(
activity.getActivityToken(), null, null, 0,
new MergedConfiguration(currentConfig, currentConfig),
false /* preserveWindow */, activityWindowInfo);
@@ -1030,8 +1030,7 @@
@NonNull
private static ClientTransaction newNewIntentTransaction(@NonNull Activity activity,
@NonNull List<ReferrerIntent> intents, boolean resume) {
- final NewIntentItem item = NewIntentItem.obtain(activity.getActivityToken(), intents,
- resume);
+ final NewIntentItem item = new NewIntentItem(activity.getActivityToken(), intents, resume);
final ClientTransaction transaction = newTransaction(activity);
transaction.addTransactionItem(item);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index 911b759..f023196 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -174,8 +174,8 @@
@Test
public void testWindowContextInfoChangeItem_execute() {
- final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
- .obtain(mWindowClientToken, mConfiguration, DEFAULT_DISPLAY);
+ final WindowContextInfoChangeItem item = new WindowContextInfoChangeItem(mWindowClientToken,
+ mConfiguration, DEFAULT_DISPLAY);
item.execute(mHandler, mPendingActions);
@@ -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/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
deleted file mode 100644
index d389b78d..0000000
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ /dev/null
@@ -1,164 +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;
-
-import static android.app.servertransaction.TestUtils.config;
-import static android.app.servertransaction.TestUtils.mergedConfig;
-import static android.app.servertransaction.TestUtils.referrerIntentList;
-import static android.app.servertransaction.TestUtils.resultInfoList;
-
-import static org.junit.Assert.assertNotSame;
-
-import android.annotation.NonNull;
-import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.res.Configuration;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.PersistableBundle;
-import android.platform.test.annotations.Presubmit;
-import android.window.ActivityWindowInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.function.Supplier;
-
-/**
- * Tests for {@link ObjectPool}.
- *
- * <p>Build/Install/Run:
- * atest FrameworksCoreTests:ObjectPoolTests
- *
- * <p>This test class is a part of Window Manager Service tests and specified in
- * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class ObjectPoolTests {
-
- @Rule
- public final MockitoRule mocks = MockitoJUnit.rule();
-
- @Mock
- private IBinder mActivityToken;
-
- // 1. Check if two obtained objects from pool are not the same.
- // 2. Check if the state of the object is cleared after recycling.
- // 3. Check if the same object is obtained from pool after recycling.
-
- @Test
- public void testRecycleActivityResultItem() {
- testRecycle(() -> ActivityResultItem.obtain(mActivityToken, resultInfoList()));
- }
-
- @Test
- public void testRecycleConfigurationChangeItem() {
- testRecycle(() -> ConfigurationChangeItem.obtain(config(), 1));
- }
-
- @Test
- public void testRecycleLaunchActivityItem() {
- final IBinder activityToken = new Binder();
- final Intent intent = new Intent("action");
- final int ident = 57;
- final ActivityInfo activityInfo = new ActivityInfo();
- activityInfo.flags = 42;
- activityInfo.setMaxAspectRatio(2.4f);
- activityInfo.launchToken = "token";
- activityInfo.applicationInfo = new ApplicationInfo();
- activityInfo.packageName = "packageName";
- activityInfo.name = "name";
- final Configuration overrideConfig = new Configuration();
- overrideConfig.assetsSeq = 5;
- final String referrer = "referrer";
- final int procState = 4;
- final Bundle bundle = new Bundle();
- bundle.putString("key", "value");
- final PersistableBundle persistableBundle = new PersistableBundle();
- persistableBundle.putInt("k", 4);
- final IBinder assistToken = new Binder();
- final IBinder shareableActivityToken = new Binder();
- final int deviceId = 3;
- final IBinder taskFragmentToken = new Binder();
- final IBinder initialCallerInfoAccessToken = new Binder();
- final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
-
- testRecycle(() -> new LaunchActivityItemBuilder(
- activityToken, intent, activityInfo)
- .setIdent(ident)
- .setCurConfig(config())
- .setOverrideConfig(overrideConfig)
- .setReferrer(referrer)
- .setProcState(procState)
- .setState(bundle)
- .setPersistentState(persistableBundle)
- .setPendingResults(resultInfoList())
- .setPendingNewIntents(referrerIntentList())
- .setIsForward(true)
- .setAssistToken(assistToken)
- .setShareableActivityToken(shareableActivityToken)
- .setTaskFragmentToken(taskFragmentToken)
- .setDeviceId(deviceId)
- .setInitialCallerInfoAccessToken(initialCallerInfoAccessToken)
- .setActivityWindowInfo(activityWindowInfo)
- .build());
- }
-
- @Test
- public void testRecycleActivityRelaunchItem() {
- testRecycle(() -> ActivityRelaunchItem.obtain(mActivityToken,
- resultInfoList(), referrerIntentList(), 42, mergedConfig(), true,
- new ActivityWindowInfo()));
- }
-
- @Test
- public void testRecycleMoveToDisplayItem() {
- testRecycle(() -> MoveToDisplayItem.obtain(mActivityToken, 4, config(),
- new ActivityWindowInfo()));
- }
-
- @Test
- public void testRecycleNewIntentItem() {
- testRecycle(() -> NewIntentItem.obtain(mActivityToken, referrerIntentList(), false));
- }
-
- private void testRecycle(@NonNull Supplier<? extends ObjectPoolItem> obtain) {
- // Reuse the same object after recycle.
- final ObjectPoolItem item = obtain.get();
- item.recycle();
- final ObjectPoolItem item2 = obtain.get();
-
- assertNotSame(item, item2); // Different instance.
-
- // Create new object when the pool is empty.
- final ObjectPoolItem item3 = obtain.get();
-
- assertNotSame(item, item3);
- }
-}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index c1b9efd..24bc547 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -271,7 +271,7 @@
@NonNull
LaunchActivityItem build() {
- return LaunchActivityItem.obtain(mActivityToken, mIntent, mIdent, mInfo,
+ return new LaunchActivityItem(mActivityToken, mIntent, mIdent, mInfo,
mCurConfig, mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor,
mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
mActivityOptions != null ? mActivityOptions.getSceneTransitionInfo() : null,
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/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 1e434bd..59d8c8d 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -74,11 +74,13 @@
@Test
public void testConfigurationChange() {
// Write to parcel
- ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config(), 1 /* deviceId */);
+ final ConfigurationChangeItem item =
+ new ConfigurationChangeItem(config(), 1 /* deviceId */);
writeAndPrepareForReading(item);
// Read from parcel and assert
- ConfigurationChangeItem result = ConfigurationChangeItem.CREATOR.createFromParcel(mParcel);
+ final ConfigurationChangeItem result =
+ ConfigurationChangeItem.CREATOR.createFromParcel(mParcel);
assertEquals(item.hashCode(), result.hashCode());
assertEquals(item, result);
@@ -108,12 +110,12 @@
final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 500, 1000),
new Rect(0, 0, 500, 500));
- MoveToDisplayItem item = MoveToDisplayItem.obtain(mActivityToken, 4 /* targetDisplayId */,
- config(), activityWindowInfo);
+ final MoveToDisplayItem item = new MoveToDisplayItem(mActivityToken,
+ 4 /* targetDisplayId */, config(), activityWindowInfo);
writeAndPrepareForReading(item);
// Read from parcel and assert
- MoveToDisplayItem result = MoveToDisplayItem.CREATOR.createFromParcel(mParcel);
+ final MoveToDisplayItem result = MoveToDisplayItem.CREATOR.createFromParcel(mParcel);
assertEquals(item.hashCode(), result.hashCode());
assertEquals(item, result);
@@ -122,11 +124,12 @@
@Test
public void testNewIntent() {
// Write to parcel
- NewIntentItem item = NewIntentItem.obtain(mActivityToken, referrerIntentList(), false);
+ final NewIntentItem item =
+ new NewIntentItem(mActivityToken, referrerIntentList(), false /* resume */);
writeAndPrepareForReading(item);
// Read from parcel and assert
- NewIntentItem result = NewIntentItem.CREATOR.createFromParcel(mParcel);
+ final NewIntentItem result = NewIntentItem.CREATOR.createFromParcel(mParcel);
assertEquals(item.hashCode(), result.hashCode());
assertEquals(item, result);
@@ -135,11 +138,11 @@
@Test
public void testActivityResult() {
// Write to parcel
- ActivityResultItem item = ActivityResultItem.obtain(mActivityToken, resultInfoList());
+ final ActivityResultItem item = new ActivityResultItem(mActivityToken, resultInfoList());
writeAndPrepareForReading(item);
// Read from parcel and assert
- ActivityResultItem result = ActivityResultItem.CREATOR.createFromParcel(mParcel);
+ final ActivityResultItem result = ActivityResultItem.CREATOR.createFromParcel(mParcel);
assertEquals(item.hashCode(), result.hashCode());
assertEquals(item, result);
@@ -163,7 +166,7 @@
// Write to parcel
final IBinder activityToken = new Binder();
final Intent intent = new Intent("action");
- int ident = 57;
+ final int ident = 57;
final ActivityInfo activityInfo = new ActivityInfo();
activityInfo.flags = 42;
activityInfo.setMaxAspectRatio(2.4f);
@@ -174,7 +177,7 @@
final Configuration overrideConfig = new Configuration();
overrideConfig.assetsSeq = 5;
final String referrer = "referrer";
- int procState = 4;
+ final int procState = 4;
final Bundle bundle = new Bundle();
bundle.putString("key", "value");
bundle.putParcelable("data", new ParcelableData(1));
@@ -216,17 +219,17 @@
@Test
public void testRelaunch() {
// Write to parcel
- Configuration overrideConfig = new Configuration();
+ final Configuration overrideConfig = new Configuration();
overrideConfig.assetsSeq = 5;
final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 500, 1000),
new Rect(0, 0, 500, 500));
- ActivityRelaunchItem item = ActivityRelaunchItem.obtain(mActivityToken, resultInfoList(),
+ final ActivityRelaunchItem item = new ActivityRelaunchItem(mActivityToken, resultInfoList(),
referrerIntentList(), 35, mergedConfig(), true, activityWindowInfo);
writeAndPrepareForReading(item);
// Read from parcel and assert
- ActivityRelaunchItem result = ActivityRelaunchItem.CREATOR.createFromParcel(mParcel);
+ final ActivityRelaunchItem result = ActivityRelaunchItem.CREATOR.createFromParcel(mParcel);
assertEquals(item.hashCode(), result.hashCode());
assertEquals(item, result);
@@ -290,8 +293,8 @@
@Test
public void testClientTransaction() {
// Write to parcel
- final NewIntentItem callback1 = NewIntentItem.obtain(
- mActivityToken, new ArrayList<>(), true);
+ final NewIntentItem callback1 =
+ new NewIntentItem(mActivityToken, new ArrayList<>(), true /* resume */);
final ActivityConfigurationChangeItem callback2 = new ActivityConfigurationChangeItem(
mActivityToken, config(), new ActivityWindowInfo());
@@ -305,7 +308,7 @@
writeAndPrepareForReading(transaction);
// Read from parcel and assert
- ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
+ final ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
assertEquals(transaction.hashCode(), result.hashCode());
assertEquals(transaction, result);
@@ -326,9 +329,7 @@
* android.app.servertransaction.TransactionParcelTests$ParcelableData".
*/
public static class ParcelableData implements Parcelable {
- int mValue;
-
- ParcelableData() {}
+ private final int mValue;
ParcelableData(int value) {
mValue = value;
@@ -344,12 +345,10 @@
dest.writeInt(mValue);
}
- public static final Creator<ParcelableData> CREATOR = new Creator<ParcelableData>() {
+ public static final Creator<ParcelableData> CREATOR = new Creator<>() {
@Override
public ParcelableData createFromParcel(Parcel source) {
- final ParcelableData data = new ParcelableData();
- data.mValue = source.readInt();
- return data;
+ return new ParcelableData(source.readInt());
}
@Override
diff --git a/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java b/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java
index 92b1c04..1d360cc 100644
--- a/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java
@@ -18,7 +18,6 @@
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
-import org.junit.Ignore;
import android.app.ActivityManager;
import android.app.activity.LocalProvider;
@@ -33,13 +32,14 @@
import android.os.UserManager;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.SystemUtil;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/ApexEnvironmentTest.java b/core/tests/coretests/src/android/content/ApexEnvironmentTest.java
index 438c5ae..f3803d2 100644
--- a/core/tests/coretests/src/android/content/ApexEnvironmentTest.java
+++ b/core/tests/coretests/src/android/content/ApexEnvironmentTest.java
@@ -20,8 +20,8 @@
import android.os.UserHandle;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
index 407c6c3..a9e781c 100644
--- a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
+++ b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
@@ -19,8 +19,8 @@
import static org.junit.Assert.fail;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/content/ContentProviderTest.java b/core/tests/coretests/src/android/content/ContentProviderTest.java
index c9a6d22..4ecd2da 100644
--- a/core/tests/coretests/src/android/content/ContentProviderTest.java
+++ b/core/tests/coretests/src/android/content/ContentProviderTest.java
@@ -26,7 +26,7 @@
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java
index c8015d4..dfde0bc 100644
--- a/core/tests/coretests/src/android/content/ContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/ContentResolverTest.java
@@ -39,7 +39,7 @@
import android.util.Size;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/content/PermissionCheckerTest.java b/core/tests/coretests/src/android/content/PermissionCheckerTest.java
index cb04a74..65d8e2b 100644
--- a/core/tests/coretests/src/android/content/PermissionCheckerTest.java
+++ b/core/tests/coretests/src/android/content/PermissionCheckerTest.java
@@ -23,7 +23,7 @@
import android.os.Binder;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java b/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java
index 4366e02c..7799185 100644
--- a/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java
+++ b/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java
@@ -22,7 +22,7 @@
import android.platform.test.annotations.AppModeFull;
import android.text.TextUtils;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
index 86e95832..ad3a16b 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
@@ -19,15 +19,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
import android.content.pm.PackageManager.Property;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
index 20421d1..b60d614 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
@@ -18,8 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java b/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java
index 61a3a11..dbd6c2b 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java
@@ -24,8 +24,8 @@
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java b/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java
index 2986d61..6c23ea3 100644
--- a/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java
@@ -24,7 +24,7 @@
import android.os.SystemProperties;
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java b/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java
index 8f8488f..ec5e205 100644
--- a/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java
+++ b/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java
@@ -23,7 +23,7 @@
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.collect.Lists;
diff --git a/core/tests/coretests/src/android/content/pm/UserInfoTest.java b/core/tests/coretests/src/android/content/pm/UserInfoTest.java
index af36dbb..edeea6d 100644
--- a/core/tests/coretests/src/android/content/pm/UserInfoTest.java
+++ b/core/tests/coretests/src/android/content/pm/UserInfoTest.java
@@ -20,8 +20,8 @@
import android.os.UserHandle;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
index 9aef2ca..85f5d69 100644
--- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
+++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
@@ -30,8 +30,8 @@
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/content/res/ResourcesDrawableTest.java b/core/tests/coretests/src/android/content/res/ResourcesDrawableTest.java
index f7f9569..ac69a0f 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesDrawableTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesDrawableTest.java
@@ -27,8 +27,8 @@
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/database/CursorWindowTest.java b/core/tests/coretests/src/android/database/CursorWindowTest.java
index 255020a..64e4d3b 100644
--- a/core/tests/coretests/src/android/database/CursorWindowTest.java
+++ b/core/tests/coretests/src/android/database/CursorWindowTest.java
@@ -23,8 +23,8 @@
import android.database.sqlite.SQLiteException;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/database/DatabaseUtilsTest.java b/core/tests/coretests/src/android/database/DatabaseUtilsTest.java
index e25fdf9..c00c171 100644
--- a/core/tests/coretests/src/android/database/DatabaseUtilsTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseUtilsTest.java
@@ -16,20 +16,19 @@
package android.database;
-import static android.database.DatabaseUtils.bindSelection;
-import static android.database.DatabaseUtils.getSqlStatementType;
-import static android.database.DatabaseUtils.getSqlStatementTypeExtended;
-import static android.database.DatabaseUtils.STATEMENT_COMMENT;
import static android.database.DatabaseUtils.STATEMENT_CREATE;
import static android.database.DatabaseUtils.STATEMENT_DDL;
import static android.database.DatabaseUtils.STATEMENT_OTHER;
import static android.database.DatabaseUtils.STATEMENT_SELECT;
import static android.database.DatabaseUtils.STATEMENT_UPDATE;
import static android.database.DatabaseUtils.STATEMENT_WITH;
+import static android.database.DatabaseUtils.bindSelection;
+import static android.database.DatabaseUtils.getSqlStatementType;
+import static android.database.DatabaseUtils.getSqlStatementTypeExtended;
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/database/RedactingCursorTest.java b/core/tests/coretests/src/android/database/RedactingCursorTest.java
index e2d2bae..470d4a9 100644
--- a/core/tests/coretests/src/android/database/RedactingCursorTest.java
+++ b/core/tests/coretests/src/android/database/RedactingCursorTest.java
@@ -24,7 +24,7 @@
import android.net.Uri;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java b/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
index 9bad6d2..efa9b7a 100644
--- a/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
+++ b/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
@@ -29,8 +29,8 @@
import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCantOpenDatabaseExceptionTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCantOpenDatabaseExceptionTest.java
index 09c380a..e20a806 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteCantOpenDatabaseExceptionTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCantOpenDatabaseExceptionTest.java
@@ -20,14 +20,12 @@
import static org.junit.Assert.fail;
import android.content.Context;
-import android.database.sqlite.SQLiteCantOpenDatabaseException;
-import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.OpenParams;
import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java
index 82bd588..a4dedc5 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java
@@ -25,8 +25,8 @@
import android.database.DatabaseUtils;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java
index 2b663bd..dbe7a9a 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java
@@ -24,8 +24,8 @@
import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index c4695d9..bd9c4b8 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -25,16 +25,13 @@
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
-import android.os.SystemClock;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.test.AndroidTestCase;
-import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
@@ -46,11 +43,9 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Phaser;
-import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
index 24d27c4..832ebe5 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
@@ -29,9 +29,9 @@
import android.os.SystemClock;
import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java
index a9d1482..ced1846 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java
@@ -20,7 +20,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
index eff52f0..5ef1460 100644
--- a/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
+++ b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
@@ -32,7 +32,7 @@
import android.util.Pair;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver;
diff --git a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
index 84d2995..4dfe2e2 100644
--- a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
+++ b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
@@ -29,8 +29,8 @@
import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import com.android.frameworks.coretests.aidl.IBpcCallbackObserver;
diff --git a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
index 552066c..98e96c2 100644
--- a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
@@ -29,13 +29,12 @@
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.Suppress;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/BluetoothBatteryStatsTest.java b/core/tests/coretests/src/android/os/BluetoothBatteryStatsTest.java
index e12ca24a..3af35f7 100644
--- a/core/tests/coretests/src/android/os/BluetoothBatteryStatsTest.java
+++ b/core/tests/coretests/src/android/os/BluetoothBatteryStatsTest.java
@@ -18,7 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/BroadcasterTest.java b/core/tests/coretests/src/android/os/BroadcasterTest.java
index 7829457..31a7063 100644
--- a/core/tests/coretests/src/android/os/BroadcasterTest.java
+++ b/core/tests/coretests/src/android/os/BroadcasterTest.java
@@ -18,8 +18,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java
index 2a718ff..e8c701e 100644
--- a/core/tests/coretests/src/android/os/BuildTest.java
+++ b/core/tests/coretests/src/android/os/BuildTest.java
@@ -23,8 +23,8 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import junit.framework.Assert;
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index 40e79ad..ded6fc5 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -30,8 +30,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
index c2cea0a..2117e74 100644
--- a/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
+++ b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
@@ -27,9 +27,9 @@
import android.util.PollingCheck;
import android.util.PollingCheck.PollingCheckCondition;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/os/CancellationSignalTest.java b/core/tests/coretests/src/android/os/CancellationSignalTest.java
index 5cd2873..8e11df5 100644
--- a/core/tests/coretests/src/android/os/CancellationSignalTest.java
+++ b/core/tests/coretests/src/android/os/CancellationSignalTest.java
@@ -21,7 +21,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/EnvironmentTest.java b/core/tests/coretests/src/android/os/EnvironmentTest.java
index 1aa263f..1b49624 100644
--- a/core/tests/coretests/src/android/os/EnvironmentTest.java
+++ b/core/tests/coretests/src/android/os/EnvironmentTest.java
@@ -32,7 +32,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 78a2c1c..66b22a8 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -60,7 +60,7 @@
import android.system.Os;
import android.util.DataUnit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
@@ -70,8 +70,6 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -79,11 +77,11 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
-import java.net.InetSocketAddress;
@RunWith(AndroidJUnit4.class)
public class FileUtilsTest {
diff --git a/core/tests/coretests/src/android/os/HandlerThreadTest.java b/core/tests/coretests/src/android/os/HandlerThreadTest.java
index 3237812..a4eda06 100644
--- a/core/tests/coretests/src/android/os/HandlerThreadTest.java
+++ b/core/tests/coretests/src/android/os/HandlerThreadTest.java
@@ -25,8 +25,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Assume;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/os/IdleHandlerTest.java b/core/tests/coretests/src/android/os/IdleHandlerTest.java
index 8644663..7accb3b 100644
--- a/core/tests/coretests/src/android/os/IdleHandlerTest.java
+++ b/core/tests/coretests/src/android/os/IdleHandlerTest.java
@@ -19,8 +19,8 @@
import android.os.MessageQueue.IdleHandler;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/LocaleListTest.java b/core/tests/coretests/src/android/os/LocaleListTest.java
index 0025e3a..251e00f 100644
--- a/core/tests/coretests/src/android/os/LocaleListTest.java
+++ b/core/tests/coretests/src/android/os/LocaleListTest.java
@@ -23,8 +23,8 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java
index 8cd6773..549e666 100644
--- a/core/tests/coretests/src/android/os/MessageQueueTest.java
+++ b/core/tests/coretests/src/android/os/MessageQueueTest.java
@@ -18,9 +18,9 @@
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.Suppress;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/PackageTagsListTest.java b/core/tests/coretests/src/android/os/PackageTagsListTest.java
index 9034a5c..6c9d7eb 100644
--- a/core/tests/coretests/src/android/os/PackageTagsListTest.java
+++ b/core/tests/coretests/src/android/os/PackageTagsListTest.java
@@ -24,7 +24,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/ParcelNullabilityTest.java b/core/tests/coretests/src/android/os/ParcelNullabilityTest.java
index ffeab29..09395f1 100644
--- a/core/tests/coretests/src/android/os/ParcelNullabilityTest.java
+++ b/core/tests/coretests/src/android/os/ParcelNullabilityTest.java
@@ -24,8 +24,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.ArrayMap;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index 0697c96..0373231 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -28,7 +28,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.Log;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/PerformanceCollectorTest.java b/core/tests/coretests/src/android/os/PerformanceCollectorTest.java
index 46f1706..436720e 100644
--- a/core/tests/coretests/src/android/os/PerformanceCollectorTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceCollectorTest.java
@@ -25,9 +25,9 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 93a5642..4b49fde 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -28,7 +28,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
index b9a12ad..3b27fc0 100644
--- a/core/tests/coretests/src/android/os/PowerManagerTest.java
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -36,8 +36,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import org.junit.After;
diff --git a/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java b/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java
index 25ce240..e22c862 100644
--- a/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java
+++ b/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java
@@ -29,7 +29,7 @@
import android.system.Os;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/os/RemoteCallbackListTest.java b/core/tests/coretests/src/android/os/RemoteCallbackListTest.java
index cc342cf..7d694a0 100644
--- a/core/tests/coretests/src/android/os/RemoteCallbackListTest.java
+++ b/core/tests/coretests/src/android/os/RemoteCallbackListTest.java
@@ -19,7 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/RemoteCallbackTest.java b/core/tests/coretests/src/android/os/RemoteCallbackTest.java
index 192c3d01..ddcc380 100644
--- a/core/tests/coretests/src/android/os/RemoteCallbackTest.java
+++ b/core/tests/coretests/src/android/os/RemoteCallbackTest.java
@@ -21,7 +21,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/ResultReceiverTest.java b/core/tests/coretests/src/android/os/ResultReceiverTest.java
index 8ee873b..be67825 100644
--- a/core/tests/coretests/src/android/os/ResultReceiverTest.java
+++ b/core/tests/coretests/src/android/os/ResultReceiverTest.java
@@ -21,7 +21,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/StrictModeTest.java b/core/tests/coretests/src/android/os/StrictModeTest.java
index 9050583..7f8af83 100644
--- a/core/tests/coretests/src/android/os/StrictModeTest.java
+++ b/core/tests/coretests/src/android/os/StrictModeTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/TestLooperManagerTest.java b/core/tests/coretests/src/android/os/TestLooperManagerTest.java
index 5959444..4d64a3a 100644
--- a/core/tests/coretests/src/android/os/TestLooperManagerTest.java
+++ b/core/tests/coretests/src/android/os/TestLooperManagerTest.java
@@ -24,8 +24,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/TimestampedValueTest.java b/core/tests/coretests/src/android/os/TimestampedValueTest.java
index f36d9e6..5bb7eb5 100644
--- a/core/tests/coretests/src/android/os/TimestampedValueTest.java
+++ b/core/tests/coretests/src/android/os/TimestampedValueTest.java
@@ -20,7 +20,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/TraceTest.java b/core/tests/coretests/src/android/os/TraceTest.java
index b2c005f..5462f32 100644
--- a/core/tests/coretests/src/android/os/TraceTest.java
+++ b/core/tests/coretests/src/android/os/TraceTest.java
@@ -20,10 +20,10 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/UserHandleTest.java b/core/tests/coretests/src/android/os/UserHandleTest.java
index 160b20d..168f2a6 100644
--- a/core/tests/coretests/src/android/os/UserHandleTest.java
+++ b/core/tests/coretests/src/android/os/UserHandleTest.java
@@ -26,7 +26,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/VibrationAttributesTest.java b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
index f5a81c5..d8142c8 100644
--- a/core/tests/coretests/src/android/os/VibrationAttributesTest.java
+++ b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/WakeLockStatsTest.java b/core/tests/coretests/src/android/os/WakeLockStatsTest.java
index f3b18c8..2e842b2 100644
--- a/core/tests/coretests/src/android/os/WakeLockStatsTest.java
+++ b/core/tests/coretests/src/android/os/WakeLockStatsTest.java
@@ -18,7 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
index fcdc590..58a434a 100644
--- a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
+++ b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
@@ -25,7 +25,7 @@
import android.platform.test.flag.junit.RavenwoodFlagsValueProvider;
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
index 85dc127..d3f4f04 100644
--- a/core/tests/coretests/src/android/os/WorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -25,7 +25,7 @@
import android.os.WorkSource.WorkChain;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/security/CredentialManagementAppTest.java b/core/tests/coretests/src/android/security/CredentialManagementAppTest.java
index fa824b1..1690741 100644
--- a/core/tests/coretests/src/android/security/CredentialManagementAppTest.java
+++ b/core/tests/coretests/src/android/security/CredentialManagementAppTest.java
@@ -23,8 +23,8 @@
import android.net.Uri;
import android.util.Xml;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainProtectionParamsTest.java b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainProtectionParamsTest.java
index ce0bf30..938147c 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainProtectionParamsTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainProtectionParamsTest.java
@@ -21,8 +21,8 @@
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java
index 37fe22f..242a273 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java
@@ -21,8 +21,8 @@
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.google.common.collect.Lists;
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/KeyDerivationParamsTest.java b/core/tests/coretests/src/android/security/keystore/recovery/KeyDerivationParamsTest.java
index 2b37b52..d310b76 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/KeyDerivationParamsTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/KeyDerivationParamsTest.java
@@ -21,8 +21,8 @@
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java b/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java
index 3b8f715..29001ae 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java
@@ -21,8 +21,8 @@
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/TrustedRootCertificatesTest.java b/core/tests/coretests/src/android/security/keystore/recovery/TrustedRootCertificatesTest.java
index 2b15d73..7677c3c 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/TrustedRootCertificatesTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/TrustedRootCertificatesTest.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java b/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java
index aec54e1..07353f8 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java
@@ -21,8 +21,8 @@
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/X509CertificateParsingUtilsTest.java b/core/tests/coretests/src/android/security/keystore/recovery/X509CertificateParsingUtilsTest.java
index ba5e74a..7f91d03 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/X509CertificateParsingUtilsTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/X509CertificateParsingUtilsTest.java
@@ -21,8 +21,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/ArrayMapTest.java b/core/tests/coretests/src/android/util/ArrayMapTest.java
index 1e444ad..711ff94 100644
--- a/core/tests/coretests/src/android/util/ArrayMapTest.java
+++ b/core/tests/coretests/src/android/util/ArrayMapTest.java
@@ -19,8 +19,8 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/ArraySetTest.java b/core/tests/coretests/src/android/util/ArraySetTest.java
index 51de634..8888991 100644
--- a/core/tests/coretests/src/android/util/ArraySetTest.java
+++ b/core/tests/coretests/src/android/util/ArraySetTest.java
@@ -18,8 +18,8 @@
import static org.junit.Assert.fail;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/Base64Test.java b/core/tests/coretests/src/android/util/Base64Test.java
index b648266..3b322c2 100644
--- a/core/tests/coretests/src/android/util/Base64Test.java
+++ b/core/tests/coretests/src/android/util/Base64Test.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Ignore;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/BinaryXmlTest.java b/core/tests/coretests/src/android/util/BinaryXmlTest.java
index da29828..96c79013 100644
--- a/core/tests/coretests/src/android/util/BinaryXmlTest.java
+++ b/core/tests/coretests/src/android/util/BinaryXmlTest.java
@@ -30,7 +30,7 @@
import android.os.PersistableBundle;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/core/tests/coretests/src/android/util/CharsetUtilsTest.java b/core/tests/coretests/src/android/util/CharsetUtilsTest.java
index fbbe311..33936e9 100644
--- a/core/tests/coretests/src/android/util/CharsetUtilsTest.java
+++ b/core/tests/coretests/src/android/util/CharsetUtilsTest.java
@@ -21,7 +21,7 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.util.HexDump;
diff --git a/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java b/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java
index 72bd578..4587f5b 100644
--- a/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java
+++ b/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java
@@ -21,8 +21,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/FloatMathTest.java b/core/tests/coretests/src/android/util/FloatMathTest.java
index f748acd..a52c9ac 100644
--- a/core/tests/coretests/src/android/util/FloatMathTest.java
+++ b/core/tests/coretests/src/android/util/FloatMathTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/KeyValueListParserTest.java b/core/tests/coretests/src/android/util/KeyValueListParserTest.java
index f65c4c7..eaf8d19 100644
--- a/core/tests/coretests/src/android/util/KeyValueListParserTest.java
+++ b/core/tests/coretests/src/android/util/KeyValueListParserTest.java
@@ -20,8 +20,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/LogNullabilityTest.java b/core/tests/coretests/src/android/util/LogNullabilityTest.java
index 475e347..5aa2626 100644
--- a/core/tests/coretests/src/android/util/LogNullabilityTest.java
+++ b/core/tests/coretests/src/android/util/LogNullabilityTest.java
@@ -21,8 +21,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/LogTest.java b/core/tests/coretests/src/android/util/LogTest.java
index 15caac9..0e44e68 100644
--- a/core/tests/coretests/src/android/util/LogTest.java
+++ b/core/tests/coretests/src/android/util/LogTest.java
@@ -19,8 +19,8 @@
import android.os.SystemProperties;
import android.test.PerformanceTestCase;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.Suppress;
-import androidx.test.runner.AndroidJUnit4;
import junit.framework.TestCase;
diff --git a/core/tests/coretests/src/android/util/LogWriterTest.java b/core/tests/coretests/src/android/util/LogWriterTest.java
index 890a401..739fbcd 100644
--- a/core/tests/coretests/src/android/util/LogWriterTest.java
+++ b/core/tests/coretests/src/android/util/LogWriterTest.java
@@ -16,7 +16,7 @@
package android.util;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/LongArrayQueueTest.java b/core/tests/coretests/src/android/util/LongArrayQueueTest.java
index 77e8d60..f696f70 100644
--- a/core/tests/coretests/src/android/util/LongArrayQueueTest.java
+++ b/core/tests/coretests/src/android/util/LongArrayQueueTest.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
index 9dbaae0d..fb0b2e3 100644
--- a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
+++ b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
@@ -18,8 +18,8 @@
import static org.junit.Assert.assertEquals;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/LruCacheTest.java b/core/tests/coretests/src/android/util/LruCacheTest.java
index 10e8308..1c6dcdf 100644
--- a/core/tests/coretests/src/android/util/LruCacheTest.java
+++ b/core/tests/coretests/src/android/util/LruCacheTest.java
@@ -20,7 +20,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java b/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java
index 06f970f..cb34c98 100644
--- a/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java
+++ b/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/MutableTest.java b/core/tests/coretests/src/android/util/MutableTest.java
index dfdff4d..1f73c16 100644
--- a/core/tests/coretests/src/android/util/MutableTest.java
+++ b/core/tests/coretests/src/android/util/MutableTest.java
@@ -20,7 +20,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/PatternsTest.java b/core/tests/coretests/src/android/util/PatternsTest.java
index a180ec3..8d0785f 100644
--- a/core/tests/coretests/src/android/util/PatternsTest.java
+++ b/core/tests/coretests/src/android/util/PatternsTest.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/PoolsTest.java b/core/tests/coretests/src/android/util/PoolsTest.java
index bdbc9b1..e31ab78 100644
--- a/core/tests/coretests/src/android/util/PoolsTest.java
+++ b/core/tests/coretests/src/android/util/PoolsTest.java
@@ -19,7 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/PrefixPrinterTest.java b/core/tests/coretests/src/android/util/PrefixPrinterTest.java
index a8d48ee..8199155 100644
--- a/core/tests/coretests/src/android/util/PrefixPrinterTest.java
+++ b/core/tests/coretests/src/android/util/PrefixPrinterTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/RecurrenceRuleTest.java b/core/tests/coretests/src/android/util/RecurrenceRuleTest.java
index 32548b4..8b2068c 100644
--- a/core/tests/coretests/src/android/util/RecurrenceRuleTest.java
+++ b/core/tests/coretests/src/android/util/RecurrenceRuleTest.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/util/SequenceUtilsTest.java b/core/tests/coretests/src/android/util/SequenceUtilsTest.java
index 6ca1751..e5ee04c 100644
--- a/core/tests/coretests/src/android/util/SequenceUtilsTest.java
+++ b/core/tests/coretests/src/android/util/SequenceUtilsTest.java
@@ -29,8 +29,8 @@
import android.platform.test.annotations.Presubmit;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/SingletonTest.java b/core/tests/coretests/src/android/util/SingletonTest.java
index 8c5a963..31ae650 100644
--- a/core/tests/coretests/src/android/util/SingletonTest.java
+++ b/core/tests/coretests/src/android/util/SingletonTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertTrue;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java
index ba9c8d9..cc5ffbe 100644
--- a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java
+++ b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java
@@ -18,8 +18,8 @@
import static org.junit.Assert.assertEquals;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/SparseLongArrayTest.java b/core/tests/coretests/src/android/util/SparseLongArrayTest.java
index b29b6f1..4038d88 100644
--- a/core/tests/coretests/src/android/util/SparseLongArrayTest.java
+++ b/core/tests/coretests/src/android/util/SparseLongArrayTest.java
@@ -21,8 +21,8 @@
import android.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/SparseSetArrayTest.java b/core/tests/coretests/src/android/util/SparseSetArrayTest.java
index a8dce70..48ea3a1 100644
--- a/core/tests/coretests/src/android/util/SparseSetArrayTest.java
+++ b/core/tests/coretests/src/android/util/SparseSetArrayTest.java
@@ -19,8 +19,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/StateSetTest.java b/core/tests/coretests/src/android/util/StateSetTest.java
index dfd1523..14e4e20 100644
--- a/core/tests/coretests/src/android/util/StateSetTest.java
+++ b/core/tests/coretests/src/android/util/StateSetTest.java
@@ -22,8 +22,8 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/TeeWriterTest.java b/core/tests/coretests/src/android/util/TeeWriterTest.java
index c78376a..cc1c091 100644
--- a/core/tests/coretests/src/android/util/TeeWriterTest.java
+++ b/core/tests/coretests/src/android/util/TeeWriterTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/XmlTest.java b/core/tests/coretests/src/android/util/XmlTest.java
index 91ebc2a..540f118 100644
--- a/core/tests/coretests/src/android/util/XmlTest.java
+++ b/core/tests/coretests/src/android/util/XmlTest.java
@@ -26,7 +26,7 @@
import android.os.PersistableBundle;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
diff --git a/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java b/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java
index 729a555..e74027e 100644
--- a/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java
+++ b/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java
@@ -21,8 +21,8 @@
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/CutoutSpecificationTest.java b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
index 7872810..0fdc239 100644
--- a/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
+++ b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
@@ -23,8 +23,8 @@
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index 0d1dde3..2c66330 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -44,8 +44,8 @@
import android.platform.test.annotations.Presubmit;
import android.view.DisplayCutout.ParcelableWrapper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/DisplayShapeTest.java b/core/tests/coretests/src/android/view/DisplayShapeTest.java
index 77dd8bd..7778ba1 100644
--- a/core/tests/coretests/src/android/view/DisplayShapeTest.java
+++ b/core/tests/coretests/src/android/view/DisplayShapeTest.java
@@ -27,8 +27,8 @@
import android.graphics.RectF;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 1682135..668487d 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -42,7 +42,7 @@
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.animation.LinearInterpolator;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index 61e05da..c3bd065 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -32,7 +32,7 @@
import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 16bd20a..1144ee1 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsSource.SIDE_BOTTOM;
import static android.view.InsetsSource.SIDE_TOP;
@@ -54,12 +55,17 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Parcel;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.SparseIntArray;
import android.view.WindowInsets.Type;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.window.flags.Flags;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -78,6 +84,9 @@
@RunWith(AndroidJUnit4.class)
public class InsetsStateTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final int ID_STATUS_BAR = InsetsSource.createId(
null /* owner */, 0 /* index */, statusBars());
private static final int ID_NAVIGATION_BAR = InsetsSource.createId(
@@ -854,4 +863,19 @@
);
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS)
+ public void testCalculateInsets_forceConsumingCaptionBar() {
+ mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true)
+ .setFlags(FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR);
+
+ final WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 1000, 1000), null, false,
+ SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
+ new SparseIntArray());
+
+ assertTrue(insets.isForceConsumingOpaqueCaptionBar());
+ }
}
diff --git a/core/tests/coretests/src/android/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java
index bad0485..d0f9a38 100644
--- a/core/tests/coretests/src/android/view/MotionEventTest.java
+++ b/core/tests/coretests/src/android/view/MotionEventTest.java
@@ -33,8 +33,8 @@
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
index b5b2d0c..8ac9292 100644
--- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
@@ -37,7 +37,7 @@
import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
import android.view.animation.LinearInterpolator;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/RoundedCornerTest.java b/core/tests/coretests/src/android/view/RoundedCornerTest.java
index 4349021..3992aa1 100644
--- a/core/tests/coretests/src/android/view/RoundedCornerTest.java
+++ b/core/tests/coretests/src/android/view/RoundedCornerTest.java
@@ -23,8 +23,8 @@
import android.graphics.Point;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/RoundedCornersTest.java b/core/tests/coretests/src/android/view/RoundedCornersTest.java
index ec665ad..c26d945 100644
--- a/core/tests/coretests/src/android/view/RoundedCornersTest.java
+++ b/core/tests/coretests/src/android/view/RoundedCornersTest.java
@@ -42,8 +42,8 @@
import android.util.DisplayMetrics;
import android.util.Pair;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
index 5f258949..bee5dc4 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
@@ -38,8 +38,8 @@
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
index dc43204..726ee85 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
@@ -35,8 +35,8 @@
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
index 71bdce4..5a5510c 100644
--- a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
+++ b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
@@ -22,8 +22,8 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.eq;
@@ -33,7 +33,7 @@
import android.content.Context;
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.AfterClass;
import org.junit.BeforeClass;
diff --git a/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java b/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java
index 65dd34f..9ab14af 100644
--- a/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java
+++ b/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java
@@ -21,16 +21,16 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
@RunWith(AndroidJUnit4.class)
@MediumTest
@Presubmit
diff --git a/core/tests/coretests/src/android/view/ViewCaptureTest.java b/core/tests/coretests/src/android/view/ViewCaptureTest.java
index 218047c..9144cd6 100644
--- a/core/tests/coretests/src/android/view/ViewCaptureTest.java
+++ b/core/tests/coretests/src/android/view/ViewCaptureTest.java
@@ -26,9 +26,9 @@
import android.view.ViewDebug.HardwareCanvasProvider;
import android.view.ViewDebug.SoftwareCanvasProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 62291d4..18364ad 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -50,10 +50,10 @@
import android.widget.ProgressBar;
import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java b/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java
index 54524b2..f5c71f8 100644
--- a/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java
@@ -25,9 +25,9 @@
import android.widget.FrameLayout;
import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/view/ViewInvalidateTest.java b/core/tests/coretests/src/android/view/ViewInvalidateTest.java
index c25a2deb..d4181d3 100644
--- a/core/tests/coretests/src/android/view/ViewInvalidateTest.java
+++ b/core/tests/coretests/src/android/view/ViewInvalidateTest.java
@@ -31,9 +31,9 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Assert;
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index ba1204b..c86045d 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -43,14 +43,14 @@
@Test
public void systemWindowInsets_afterConsuming_isConsumed() {
assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null,
- null, false, 0, 0, null, null, null, null,
+ null, false, 0, false, 0, null, null, null, null,
WindowInsets.Type.systemBars(), false, null, null, 0, 0)
.consumeSystemWindowInsets().isConsumed());
}
@Test
public void multiNullConstructor_isConsumed() {
- assertTrue(new WindowInsets(null, null, null, false, 0, 0, null, null, null, null,
+ assertTrue(new WindowInsets(null, null, null, false, 0, false, 0, null, null, null, null,
WindowInsets.Type.systemBars(), false, null, null, 0, 0).isConsumed());
}
@@ -67,7 +67,7 @@
WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0));
WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, 0,
- 0, null, null, null, DisplayShape.NONE, systemBars(),
+ false, 0, null, null, null, DisplayShape.NONE, systemBars(),
true /* compatIgnoreVisibility */, null, null, 0, 0);
assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index dd8cc6e..1f649b1 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -1053,6 +1053,28 @@
assertFalse(mAccessibilityCache.isNodeInCache(childInfo));
}
+ @Test
+ public void getEventSourceClassName_windowStateChangedThenRemoved() {
+ final String sourceActivityClassName = "com.example.SomeActivity";
+ final AccessibilityEvent windowStateChangedEvent = new AccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ final View mockView = getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1);
+ windowStateChangedEvent.setSource(mockView);
+ windowStateChangedEvent.setClassName(sourceActivityClassName);
+
+ mAccessibilityCache.onAccessibilityEvent(windowStateChangedEvent);
+ assertEquals(mAccessibilityCache.getEventSourceClassName(WINDOW_ID_1),
+ sourceActivityClassName);
+
+ final AccessibilityEvent windowRemovedEvent = new AccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOWS_CHANGED);
+ windowRemovedEvent.setWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED);
+ windowRemovedEvent.setSource(mockView);
+
+ mAccessibilityCache.onAccessibilityEvent(windowRemovedEvent);
+ assertNull(mAccessibilityCache.getEventSourceClassName(WINDOW_ID_1));
+ }
+
private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) {
AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain();
windowInfo.setId(windowId);
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 2d82d23..3d4918b 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
// The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
// See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
// and assertAccessibilityNodeInfoCleared in that class.
- private static final int NUM_MARSHALLED_PROPERTIES = 44;
+ private static final int NUM_MARSHALLED_PROPERTIES = 43;
/**
* The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java b/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java
index e1b403f..c5e0ebd 100644
--- a/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java
@@ -26,8 +26,8 @@
import android.os.Bundle;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java b/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java
index 46e3a4c..1c26da7 100644
--- a/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java
@@ -20,8 +20,8 @@
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java b/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java
index e4cfc53..4635e9b 100644
--- a/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java
@@ -20,11 +20,10 @@
import static org.testng.Assert.assertThrows;
-
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
index 20a8768..07fe12f 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
@@ -20,8 +20,8 @@
import android.provider.DeviceConfig;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
index 8225afc..2ed016c 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
@@ -38,8 +38,8 @@
import android.view.View;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierEventTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierEventTest.java
index 11eb567..d92da6e 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierEventTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierEventTest.java
@@ -19,8 +19,8 @@
import android.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java
index 011866d..ec46426 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java
@@ -20,8 +20,8 @@
import static org.testng.Assert.assertThrows;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java
index 31f8029..de6f1d2 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java
@@ -24,8 +24,8 @@
import android.os.Bundle;
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
index 4f0b44b..bd1f7e1 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
@@ -25,8 +25,8 @@
import android.os.Parcel;
import android.util.ArrayMap;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java
index 14c077c..61e6738 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java
@@ -36,8 +36,8 @@
import android.os.Parcel;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java b/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java
index ceea6ca..e36a7f1 100644
--- a/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java
@@ -28,9 +28,9 @@
import android.util.AttributeSet;
import android.util.PollingCheck;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.WidgetTestUtils;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/AbsSeekBarTest.java b/core/tests/coretests/src/android/widget/AbsSeekBarTest.java
index ccd873d..451fcf4 100644
--- a/core/tests/coretests/src/android/widget/AbsSeekBarTest.java
+++ b/core/tests/coretests/src/android/widget/AbsSeekBarTest.java
@@ -31,9 +31,9 @@
import android.graphics.drawable.shapes.RectShape;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/widget/AppWidgetHostViewTest.java b/core/tests/coretests/src/android/widget/AppWidgetHostViewTest.java
index 6edc162..6a8b426 100644
--- a/core/tests/coretests/src/android/widget/AppWidgetHostViewTest.java
+++ b/core/tests/coretests/src/android/widget/AppWidgetHostViewTest.java
@@ -28,8 +28,8 @@
import android.view.ViewGroup.OnHierarchyChangeListener;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/DateTimeViewTest.java b/core/tests/coretests/src/android/widget/DateTimeViewTest.java
index 14b48ed..a8fd913 100644
--- a/core/tests/coretests/src/android/widget/DateTimeViewTest.java
+++ b/core/tests/coretests/src/android/widget/DateTimeViewTest.java
@@ -21,8 +21,8 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index c37a34a..8f52d23 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -48,10 +48,10 @@
import android.view.View;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.Suppress;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/EditorCursorTest.java b/core/tests/coretests/src/android/widget/EditorCursorTest.java
index 585c601..9513aa5 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorTest.java
@@ -34,9 +34,9 @@
import android.app.Instrumentation;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
index 5d62f1c..981f1db 100644
--- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -29,9 +29,9 @@
import android.util.AttributeSet;
import android.util.PollingCheck;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/NumberPickerTest.java b/core/tests/coretests/src/android/widget/NumberPickerTest.java
index cab7c89..386972a 100644
--- a/core/tests/coretests/src/android/widget/NumberPickerTest.java
+++ b/core/tests/coretests/src/android/widget/NumberPickerTest.java
@@ -24,9 +24,9 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/widget/ProgressBarTest.java b/core/tests/coretests/src/android/widget/ProgressBarTest.java
index 5b92471..fa6dd31 100644
--- a/core/tests/coretests/src/android/widget/ProgressBarTest.java
+++ b/core/tests/coretests/src/android/widget/ProgressBarTest.java
@@ -26,9 +26,9 @@
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
index 534420e..b021b62 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -41,8 +41,8 @@
import android.view.View;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
import com.android.internal.widget.IRemoteViewsFactory;
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsProtoTest.java b/core/tests/coretests/src/android/widget/RemoteViewsProtoTest.java
index 8e9ba7b..7c14032 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsProtoTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsProtoTest.java
@@ -26,8 +26,8 @@
import android.view.View;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index c8ea374..0621d82 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -34,9 +34,6 @@
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.AsyncTask;
@@ -53,8 +50,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
index 6d0bab5..3dfb3f5 100644
--- a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
@@ -30,9 +30,9 @@
import android.util.AttributeSet;
import android.util.PollingCheck;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index a0cfb31..3de4893 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -47,9 +47,9 @@
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 9cf2e42..3eb7d9a 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -88,10 +88,10 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.action.EspressoKey;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.Suppress;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
index 777246b..12f8c9c 100644
--- a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
@@ -41,9 +41,9 @@
import android.view.textclassifier.TextClassification;
import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/TextViewPerformanceTest.java b/core/tests/coretests/src/android/widget/TextViewPerformanceTest.java
index a769ea4..ff99db3 100644
--- a/core/tests/coretests/src/android/widget/TextViewPerformanceTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewPerformanceTest.java
@@ -25,9 +25,9 @@
import android.view.ViewGroup;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java b/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java
index 91266f0..8043a66 100644
--- a/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java
@@ -26,9 +26,9 @@
import android.widget.TextView.BufferType;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java b/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java
index 98bf87b..b61d868 100644
--- a/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java
@@ -47,9 +47,9 @@
import android.view.inputmethod.InputContentInfo;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index f5582d0..769a440 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -49,9 +49,9 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java
index 4eccbe5..a466caf 100644
--- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java
@@ -22,7 +22,7 @@
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.content.om.OverlayConfigParser;
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
index a0e9947..43cff8d 100644
--- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
@@ -28,7 +28,7 @@
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.frameworks.coretests.R;
import com.android.internal.content.om.OverlayConfig;
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java
index 8d825e4..7928e6a 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java
@@ -26,8 +26,8 @@
import android.os.Build;
import android.os.ConditionVariable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.SystemUtil;
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index b70f290..5f8ab28 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -35,8 +35,8 @@
import android.util.ArrayMap;
import android.util.SparseArray;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BinderInternal.CallSession;
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
index 27398ea..66de3d7 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
@@ -34,8 +34,8 @@
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java b/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java
index a1b80d2..13d72137 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java
@@ -23,7 +23,7 @@
import android.os.Binder;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.os.BinderCallHeavyHitterWatcher.HeavyHitterContainer;
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java
index b2054f1..c6baea3 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java
@@ -22,8 +22,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
index 31b55e6..3355cc3 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
@@ -29,8 +29,8 @@
import android.util.ArrayMap;
import android.util.proto.ProtoOutputStream;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BinderInternal.CallSession;
import com.android.internal.os.BinderLatencyObserver.LatencyDims;
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java
index 5f02f04..9acd991 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java
@@ -23,8 +23,8 @@
import android.os.FileUtils;
import android.util.IntArray;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java b/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java
index 2da3873..ca97472 100644
--- a/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java
@@ -20,7 +20,7 @@
import android.os.FileUtils;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
index 1d8628d..8fd87c0 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
@@ -28,8 +28,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java
index a57a400..78cf65c 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java
@@ -28,8 +28,8 @@
import android.platform.test.annotations.Presubmit;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
index 7eac2a3..c3d4b83 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
@@ -30,8 +30,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
index 610e6ae..b75ad7f 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
@@ -28,16 +28,15 @@
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.runner.RunWith;
import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -50,7 +49,6 @@
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
-import java.util.stream.IntStream;
@SmallTest
@RunWith(AndroidJUnit4.class)
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
index 6507226..864e198 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
@@ -28,8 +28,8 @@
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java b/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java
index ad5186e..a74f339 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java
@@ -22,8 +22,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.LongSparseLongArray;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
index f42d26d..cdfef25 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
@@ -22,8 +22,8 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java b/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java
index 632dce0..0a0ad39 100644
--- a/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java
@@ -19,7 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index fa5d72a..b86dc58 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -24,8 +24,8 @@
import android.os.Parcel;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
index 78ef92b..b8f7903 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
@@ -24,8 +24,8 @@
import android.os.Parcel;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
index dfb5cc3..8bb0ee7 100644
--- a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
@@ -25,8 +25,8 @@
import android.platform.test.annotations.Presubmit;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Assert;
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
index 7ffc7b2..cff57f4 100644
--- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -20,8 +20,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
index 951fa98..30dd50e 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
@@ -33,8 +33,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.Xml;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.power.ModemPowerProfile;
import com.android.internal.util.XmlUtils;
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
index 4846ed27..046f747 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -26,8 +26,8 @@
import android.util.SparseArray;
import android.util.Xml;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
index f61fc7c..a05a44c5 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
@@ -21,8 +21,8 @@
import android.os.FileUtils;
import android.util.IntArray;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java b/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java
index 3e4f34d..7c7e2ed 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java
@@ -20,8 +20,8 @@
import android.os.FileUtils;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java
index a706350..93dd09d 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java
@@ -24,8 +24,8 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
index cc6c4e8..26f5955 100644
--- a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
@@ -23,8 +23,8 @@
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
index a978e3b..7b9ea55 100644
--- a/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
@@ -21,9 +21,9 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/com/android/internal/util/BitUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/BitUtilsTest.java
index fdde36a..fdba811 100644
--- a/core/tests/coretests/src/com/android/internal/util/BitUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/BitUtilsTest.java
@@ -30,8 +30,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/CollectionUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/CollectionUtilsTest.java
index ac954d6a..32c2d63 100644
--- a/core/tests/coretests/src/com/android/internal/util/CollectionUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/CollectionUtilsTest.java
@@ -22,7 +22,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java
index e6ebfef..aa59afe 100644
--- a/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java
@@ -33,8 +33,8 @@
import android.text.style.TextAppearanceSpan;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
diff --git a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
index d2d3c13..7bd062a 100644
--- a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
@@ -31,7 +31,7 @@
import android.content.ComponentName;
import android.util.SparseArray;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java b/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java
index 61d4e3d..9259181f 100644
--- a/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java
@@ -19,7 +19,7 @@
import android.util.Dumpable;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.util.dump.DumpableContainerImpl;
diff --git a/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java
index 6bd67ea..aee352b 100644
--- a/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java
@@ -28,7 +28,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.provider.DeviceConfig;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/util/FastMathTest.java b/core/tests/coretests/src/com/android/internal/util/FastMathTest.java
index dd26334..bedcf4c 100644
--- a/core/tests/coretests/src/com/android/internal/util/FastMathTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/FastMathTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/GrowingArrayUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/GrowingArrayUtilsTest.java
index 8456161..a0eb058 100644
--- a/core/tests/coretests/src/com/android/internal/util/GrowingArrayUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/GrowingArrayUtilsTest.java
@@ -23,7 +23,7 @@
import android.util.EmptyArray;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java b/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java
index dcffa1c..9adf607 100644
--- a/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java
@@ -22,7 +22,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/IntPairTest.java b/core/tests/coretests/src/com/android/internal/util/IntPairTest.java
index af6503f..527be8f 100644
--- a/core/tests/coretests/src/com/android/internal/util/IntPairTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/IntPairTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
index 010f724..ce265a3 100644
--- a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
@@ -29,7 +29,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.provider.DeviceConfig;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.util.LatencyTracker.ActionProperties;
diff --git a/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java b/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java
index e6418fa..93262f0 100644
--- a/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java
index d24cbfe..b22014e 100644
--- a/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java b/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java
index 0d21335..e0d5499 100644
--- a/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java
@@ -21,7 +21,7 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/com/android/internal/util/RingBufferTest.java b/core/tests/coretests/src/com/android/internal/util/RingBufferTest.java
index d7a100a..4497770 100644
--- a/core/tests/coretests/src/com/android/internal/util/RingBufferTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/RingBufferTest.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/SizedInputStreamTest.java b/core/tests/coretests/src/com/android/internal/util/SizedInputStreamTest.java
index efef7ff..dbd6fc1 100644
--- a/core/tests/coretests/src/com/android/internal/util/SizedInputStreamTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/SizedInputStreamTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java b/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
index ef579fe..43ee3c5 100644
--- a/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
@@ -24,7 +24,7 @@
import android.os.SystemClock;
import android.text.format.DateUtils;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index 2b8adcb..ad6fe8d 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -40,8 +40,8 @@
import android.widget.Toolbar;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index 92a7d8e..00b4f46 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -19,10 +19,10 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
import static com.google.common.truth.Truth.assertThat;
@@ -54,8 +54,8 @@
import android.test.mock.MockContentResolver;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.test.FakeSettingsProvider;
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
index bf9221a..548911b 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
@@ -35,8 +35,8 @@
import android.widget.flags.Flags;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import com.google.common.truth.Expect;
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/core/tests/vibrator/src/android/os/VibratorTest.java b/core/tests/vibrator/src/android/os/VibratorTest.java
index cfa12bb..6210a00 100644
--- a/core/tests/vibrator/src/android/os/VibratorTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorTest.java
@@ -222,6 +222,18 @@
}
@Test
+ public void vibrate_withVibrationAttributesAndReason_usesGivenAttributesAndReason() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ VibrationAttributes attributes = new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_TOUCH).build();
+ String reason = "reason";
+
+ mVibratorSpy.vibrate(effect, attributes, reason);
+
+ verify(mVibratorSpy).vibrate(anyInt(), anyString(), eq(effect), eq(reason), eq(attributes));
+ }
+
+ @Test
public void vibrate_withAudioAttributes_createsVibrationAttributesWithSameUsage() {
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 5d4139e..1fe6ca7 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -505,6 +505,7 @@
<permission name="android.permission.RENOUNCE_PERMISSIONS" />
<permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
<permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
+ <permission name="android.permission.READ_DROPBOX_DATA" />
<permission name="android.permission.READ_LOGS" />
<permission name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
<permission name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index b83931f..df95a91 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -35,7 +35,6 @@
import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
import android.os.LocaleList;
-import android.text.ClientFlags;
import android.text.GraphicsOperations;
import android.text.SpannableString;
import android.text.SpannedString;
@@ -1541,21 +1540,8 @@
* @return typeface
*/
public Typeface setTypeface(Typeface typeface) {
- return setTypefaceInternal(typeface, true);
- }
-
- private Typeface setTypefaceInternal(Typeface typeface, boolean clearFontVariationSettings) {
final long typefaceNative = typeface == null ? 0 : typeface.native_instance;
nSetTypeface(mNativePaint, typefaceNative);
-
- if (ClientFlags.clearFontVariationSettings()) {
- if (clearFontVariationSettings && !Objects.equals(mTypeface, typeface)) {
- // We cannot call setFontVariationSetting with empty string or null because it calls
- // setTypeface method. To avoid recursive setTypeface call, manually resetting
- // mFontVariationSettings.
- mFontVariationSettings = null;
- }
- }
mTypeface = typeface;
return typeface;
}
@@ -2051,14 +2037,6 @@
* </li>
* </ul>
*
- * Note: This method replaces the Typeface previously set to this instance.
- * Until API {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, any caller of
- * {@link #setTypeface(Typeface)} should call this method with empty settings, then call
- * {@link #setTypeface(Typeface)}, then call this method with preferred variation settings.
- * The device API more than {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, the
- * {@link #setTypeface(Typeface)} method clears font variation settings. So caller of
- * {@link #setTypeface(Typeface)} should call this method again for applying variation settings.
- *
* @param fontVariationSettings font variation settings. You can pass null or empty string as
* no variation settings.
*
@@ -2081,8 +2059,8 @@
if (settings == null || settings.length() == 0) {
mFontVariationSettings = null;
- setTypefaceInternal(Typeface.createFromTypefaceWithVariation(mTypeface,
- Collections.emptyList()), false);
+ setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface,
+ Collections.emptyList()));
return true;
}
@@ -2100,8 +2078,7 @@
return false;
}
mFontVariationSettings = settings;
- setTypefaceInternal(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes),
- false);
+ setTypeface(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes));
return true;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index 544f0f3..882a8d0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -565,6 +565,7 @@
return true;
}
+ // Only called by onTouch() and mRenderer is already null-checked.
@GuardedBy("mLock")
private void onStartDragging(@NonNull MotionEvent event) {
mVelocityTracker = VelocityTracker.obtain();
@@ -590,6 +591,7 @@
});
}
+ // Only called by onTouch() and mRenderer is already null-checked.
@GuardedBy("mLock")
private void onDrag(@NonNull MotionEvent event) {
if (mVelocityTracker != null) {
@@ -660,8 +662,10 @@
@GuardedBy("mLock")
private void updateDividerPosition(int position) {
- mRenderer.setDividerPosition(position);
- mRenderer.updateSurface();
+ if (mRenderer != null) {
+ mRenderer.setDividerPosition(position);
+ mRenderer.updateSurface();
+ }
}
@GuardedBy("mLock")
@@ -669,7 +673,10 @@
// Veil visibility change should be applied together with the surface boost transaction in
// the wct.
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- mRenderer.hideVeils(t);
+
+ if (mRenderer != null) {
+ mRenderer.hideVeils(t);
+ }
// Callbacks must be executed on the executor to release mLock and prevent deadlocks.
// mDecorSurfaceOwner may change between here and when the callback is executed,
@@ -684,8 +691,10 @@
}
});
});
- mRenderer.mIsDragging = false;
- mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
+ if (mRenderer != null) {
+ mRenderer.mIsDragging = false;
+ mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
+ }
}
/**
@@ -1090,13 +1099,14 @@
@NonNull
private final SurfaceControl mDividerSurface;
@NonNull
+ private final SurfaceControl mDividerLineSurface;
+ @NonNull
private final WindowlessWindowManager mWindowlessWindowManager;
@NonNull
private final SurfaceControlViewHost mViewHost;
@NonNull
private final FrameLayout mDividerLayout;
- @NonNull
- private final View mDividerLine;
+ @Nullable
private View mDragHandle;
@NonNull
private final View.OnTouchListener mListener;
@@ -1115,7 +1125,10 @@
mProperties = properties;
mListener = listener;
- mDividerSurface = createChildSurface("DividerSurface", true /* visible */);
+ mDividerSurface = createChildSurface(
+ mProperties.mDecorSurface, "DividerSurface", true /* visible */);
+ mDividerLineSurface = createChildSurface(
+ mDividerSurface, "DividerLineSurface", true /* visible */);
mWindowlessWindowManager = new WindowlessWindowManager(
mProperties.mConfiguration,
mDividerSurface,
@@ -1127,7 +1140,6 @@
context, displayManager.getDisplay(mProperties.mDisplayId),
mWindowlessWindowManager, "DividerContainer");
mDividerLayout = new FrameLayout(context);
- mDividerLine = new View(context);
update();
}
@@ -1220,6 +1232,7 @@
dividerSurfacePosition = mDividerPosition;
}
+ // Update the divider surface position relative to the decor surface
if (mProperties.mIsVerticalSplit) {
t.setPosition(mDividerSurface, dividerSurfacePosition, 0.0f);
t.setWindowCrop(mDividerSurface, mDividerSurfaceWidthPx, taskBounds.height());
@@ -1228,10 +1241,24 @@
t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerSurfaceWidthPx);
}
- // Update divider line position in the surface
+ // Update divider line surface position relative to the divider surface
final int offset = mDividerPosition - dividerSurfacePosition;
- mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0);
- mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset);
+ if (mProperties.mIsVerticalSplit) {
+ t.setPosition(mDividerLineSurface, offset, 0);
+ t.setWindowCrop(mDividerLineSurface,
+ mProperties.mDividerWidthPx, taskBounds.height());
+ } else {
+ t.setPosition(mDividerLineSurface, 0, offset);
+ t.setWindowCrop(mDividerLineSurface,
+ taskBounds.width(), mProperties.mDividerWidthPx);
+ }
+
+ // Update divider line surface visibility and color.
+ // If a container is fully expanded, the divider line is invisible unless dragging.
+ final boolean isDividerLineVisible = !mProperties.mIsDraggableExpandType || mIsDragging;
+ t.setVisibility(mDividerLineSurface, isDividerLineVisible);
+ t.setColor(mDividerLineSurface, colorToFloatArray(
+ Color.valueOf(mProperties.mDividerAttributes.getDividerColor())));
if (mIsDragging) {
updateVeils(t);
@@ -1277,21 +1304,6 @@
*/
private void updateDivider(@NonNull SurfaceControl.Transaction t) {
mDividerLayout.removeAllViews();
- mDividerLayout.addView(mDividerLine);
- if (mProperties.mIsDraggableExpandType && !mIsDragging) {
- // If a container is fully expanded, the divider overlays on the expanded container.
- mDividerLine.setBackgroundColor(Color.TRANSPARENT);
- } else {
- mDividerLine.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor());
- }
- final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
- mDividerLine.setLayoutParams(
- mProperties.mIsVerticalSplit
- ? new FrameLayout.LayoutParams(
- mProperties.mDividerWidthPx, taskBounds.height())
- : new FrameLayout.LayoutParams(
- taskBounds.width(), mProperties.mDividerWidthPx)
- );
if (mProperties.mDividerAttributes.getDividerType()
== DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
createVeils();
@@ -1345,10 +1357,11 @@
}
@NonNull
- private SurfaceControl createChildSurface(@NonNull String name, boolean visible) {
+ private SurfaceControl createChildSurface(
+ @NonNull SurfaceControl parent, @NonNull String name, boolean visible) {
final Rect bounds = mProperties.mConfiguration.windowConfiguration.getBounds();
return new SurfaceControl.Builder()
- .setParent(mProperties.mDecorSurface)
+ .setParent(parent)
.setName(name)
.setHidden(!visible)
.setCallsite("DividerManager.createChildSurface")
@@ -1359,10 +1372,12 @@
private void createVeils() {
if (mPrimaryVeil == null) {
- mPrimaryVeil = createChildSurface("DividerPrimaryVeil", false /* visible */);
+ mPrimaryVeil = createChildSurface(
+ mProperties.mDecorSurface, "DividerPrimaryVeil", false /* visible */);
}
if (mSecondaryVeil == null) {
- mSecondaryVeil = createChildSurface("DividerSecondaryVeil", false /* visible */);
+ mSecondaryVeil = createChildSurface(
+ mProperties.mDecorSurface, "DividerSecondaryVeil", false /* visible */);
}
}
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 864f7cd..49d9029 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -17,6 +17,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/handle_menu"
android:layout_width="@dimen/desktop_mode_handle_menu_width"
android:layout_height="wrap_content"
android:clipChildren="false"
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 4e7cfb6..8669af3 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -263,7 +263,7 @@
<!-- Accessibility text for the caption back button [CHAR LIMIT=NONE] -->
<string name="back_button_text">Back</string>
<!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] -->
- <string name="handle_text">Handle</string>
+ <string name="handle_text">App handle</string>
<!-- Accessibility text for the handle menu app icon [CHAR LIMIT=NONE] -->
<string name="app_icon_text">App Icon</string>
<!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
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/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index d4d9d00..fdb4523 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -372,6 +372,7 @@
// ==> activity view
// ==> manage button
bringChildToFront(mManageButton);
+ setManageClickListener();
applyThemeAttrs();
@@ -502,6 +503,7 @@
R.layout.bubble_manage_button, this /* parent */, false /* attach */);
addView(mManageButton);
mManageButton.setVisibility(visibility);
+ setManageClickListener();
post(() -> {
int touchAreaHeight =
getResources().getDimensionPixelSize(
@@ -646,9 +648,8 @@
}
}
- // TODO: Could listener be passed when we pass StackView / can we avoid setting this like this
- void setManageClickListener(OnClickListener manageClickListener) {
- mManageButton.setOnClickListener(manageClickListener);
+ private void setManageClickListener() {
+ mManageButton.setOnClickListener(v -> mStackView.onManageBubbleClicked());
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index f93f19d..8f8b77b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1374,7 +1374,6 @@
// The menu itself should respect locale direction so the icons are on the correct side.
mManageMenu.setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
addView(mManageMenu);
- updateManageButtonListener();
}
/**
@@ -3375,14 +3374,6 @@
mExpandedViewContainer.setAlpha(0f);
mExpandedViewContainer.addView(bev);
- postDelayed(() -> {
- // Set the Manage button click handler from postDelayed. This appears to resolve
- // a race condition with adding the BubbleExpandedView view to the expanded view
- // container. Due to the race condition the click handler sometimes is not set up
- // correctly and is never called.
- updateManageButtonListener();
- }, 0);
-
if (!mIsExpansionAnimating) {
mIsBubbleSwitchAnimating = true;
mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
@@ -3392,13 +3383,8 @@
}
}
- private void updateManageButtonListener() {
- BubbleExpandedView bev = getExpandedView();
- if (mIsExpanded && bev != null) {
- bev.setManageClickListener((view) -> {
- showManageMenu(true /* show */);
- });
- }
+ void onManageBubbleClicked() {
+ showManageMenu(true /* show */);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index d54a6b0..c91567d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -80,6 +80,7 @@
outline.setPath(mPath);
}
});
+ setContentDescription(getResources().getString(R.string.handle_text));
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 86cec02..84e32a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -81,6 +81,7 @@
private boolean mHasNavigationBar = false;
private boolean mHasStatusBar = false;
private int mNavBarFrameHeight = 0;
+ private int mTaskbarFrameHeight = 0;
private boolean mAllowSeamlessRotationDespiteNavBarMoving = false;
private boolean mNavigationBarCanMove = false;
private boolean mReverseDefaultRotation = false;
@@ -119,6 +120,7 @@
&& mNavigationBarCanMove == other.mNavigationBarCanMove
&& mReverseDefaultRotation == other.mReverseDefaultRotation
&& mNavBarFrameHeight == other.mNavBarFrameHeight
+ && mTaskbarFrameHeight == other.mTaskbarFrameHeight
&& Objects.equals(mInsetsState, other.mInsetsState);
}
@@ -126,7 +128,7 @@
public int hashCode() {
return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi,
mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar,
- mNavBarFrameHeight, mAllowSeamlessRotationDespiteNavBarMoving,
+ mNavBarFrameHeight, mTaskbarFrameHeight, mAllowSeamlessRotationDespiteNavBarMoving,
mNavigationBarCanMove, mReverseDefaultRotation, mInsetsState);
}
@@ -176,6 +178,7 @@
mNavigationBarCanMove = dl.mNavigationBarCanMove;
mReverseDefaultRotation = dl.mReverseDefaultRotation;
mNavBarFrameHeight = dl.mNavBarFrameHeight;
+ mTaskbarFrameHeight = dl.mTaskbarFrameHeight;
mNonDecorInsets.set(dl.mNonDecorInsets);
mStableInsets.set(dl.mStableInsets);
mInsetsState.set(dl.mInsetsState, true /* copySources */);
@@ -214,7 +217,8 @@
if (mHasStatusBar) {
convertNonDecorInsetsToStableInsets(res, mStableInsets, mCutout, mHasStatusBar);
}
- mNavBarFrameHeight = getNavigationBarFrameHeight(res, mWidth > mHeight);
+ mNavBarFrameHeight = getNavigationBarFrameHeight(res, /* landscape */ mWidth > mHeight);
+ mTaskbarFrameHeight = SystemBarUtils.getTaskbarHeight(res);
}
/**
@@ -321,6 +325,17 @@
outBounds.inset(mStableInsets);
}
+ /** Predicts the calculated stable bounds when in Desktop Mode. */
+ public void getStableBoundsForDesktopMode(Rect outBounds) {
+ getStableBounds(outBounds);
+
+ if (mNavBarFrameHeight != mTaskbarFrameHeight) {
+ // Currently not in pinned taskbar mode, exclude taskbar insets instead of current
+ // navigation insets from bounds.
+ outBounds.bottom = mHeight - mTaskbarFrameHeight;
+ }
+ }
+
/**
* Gets navigation bar position for this layout
* @return Navigation bar position for this layout.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ImeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ImeListener.kt
new file mode 100644
index 0000000..a34d7be
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ImeListener.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.wm.shell.common
+
+import android.graphics.Rect
+import android.view.InsetsSource
+import android.view.InsetsState
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener
+
+abstract class ImeListener(
+ private val mDisplayController: DisplayController,
+ private val mDisplayId: Int
+) : OnInsetsChangedListener {
+ // The last insets state
+ private val mInsetsState = InsetsState()
+ private val mTmpBounds = Rect()
+
+ override fun insetsChanged(insetsState: InsetsState) {
+ if (mInsetsState == insetsState) {
+ return
+ }
+
+ // Get the stable bounds that account for display cutout and system bars to calculate the
+ // relative IME height
+ val layout = mDisplayController.getDisplayLayout(mDisplayId)
+ if (layout == null) {
+ return
+ }
+ layout.getStableBounds(mTmpBounds)
+
+ val wasVisible = getImeVisibilityAndHeight(mInsetsState).first
+ val oldHeight = getImeVisibilityAndHeight(mInsetsState).second
+
+ val isVisible = getImeVisibilityAndHeight(insetsState).first
+ val newHeight = getImeVisibilityAndHeight(insetsState).second
+
+ mInsetsState.set(insetsState, true)
+ if (wasVisible != isVisible || oldHeight != newHeight) {
+ onImeVisibilityChanged(isVisible, newHeight)
+ }
+ }
+
+ private fun getImeVisibilityAndHeight(
+ insetsState: InsetsState): Pair<Boolean, Int> {
+ val source = insetsState.peekSource(InsetsSource.ID_IME)
+ val frame = if (source != null && source.isVisible) source.frame else null
+ val height = if (frame != null) mTmpBounds.bottom - frame.top else 0
+ val visible = source?.isVisible ?: false
+ return Pair(visible, height)
+ }
+
+ /**
+ * To be overridden by implementations to handle IME changes.
+ */
+ protected abstract fun onImeVisibilityChanged(imeVisible: Boolean, imeHeight: Int)
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 5097ed8..19a109e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -31,6 +31,7 @@
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -201,7 +202,7 @@
/** Showing resizing hint. */
public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
- boolean immediately, float[] veilColor) {
+ boolean immediately) {
if (mResizingIconView == null) {
return;
}
@@ -234,7 +235,7 @@
if (mBackgroundLeash == null) {
mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
- t.setColor(mBackgroundLeash, veilColor)
+ t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
.setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
}
@@ -245,7 +246,7 @@
mGapBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
GAP_BACKGROUND_SURFACE_NAME, mSurfaceSession);
// Fill up another side bounds area.
- t.setColor(mGapBackgroundLeash, veilColor)
+ t.setColor(mGapBackgroundLeash, getResizingBackgroundColor(resizingTask))
.setLayer(mGapBackgroundLeash, Integer.MAX_VALUE - 2)
.setPosition(mGapBackgroundLeash, left, top)
.setWindowCrop(mGapBackgroundLeash, sideBounds.width(), sideBounds.height());
@@ -486,4 +487,9 @@
mIcon = null;
}
}
+
+ private static float[] getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
+ final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
+ return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).getComponents();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index e8226051..f9259e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.common.split;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED;
+
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -24,18 +26,25 @@
import android.app.ActivityManager;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Color;
import android.graphics.Rect;
+import android.os.UserHandle;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.util.ArrayUtils;
import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
+import java.util.Arrays;
+import java.util.List;
+
/** Helper utility class for split screen components to use. */
public class SplitScreenUtils {
/** Reverse the split position. */
@@ -128,10 +137,4 @@
return isLandscape;
}
}
-
- /** Returns the specified background color that matches a RunningTaskInfo. */
- public static Color getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
- final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
- return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor);
- }
}
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..d289ef2 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.DYNAMIC_INITIAL_BOUNDS.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/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 247cc42..4299841 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -32,77 +32,77 @@
import java.util.concurrent.Executor
import java.util.function.Consumer
-/** Keeps track of task data related to desktop mode. */
+/** Tracks task data for Desktop Mode. */
class DesktopModeTaskRepository {
- /** Task data that is tracked per display */
- private data class DisplayData(
- /**
- * Set of task ids that are marked as active in desktop mode. Active tasks in desktop mode
- * are freeform tasks that are visible or have been visible after desktop mode was
- * activated. Task gets removed from this list when it vanishes. Or when desktop mode is
- * turned off.
- */
+ /**
+ * Task data tracked per desktop.
+ *
+ * @property activeTasks task ids of active tasks currently or previously visible in Desktop
+ * mode session. Tasks become inactive when task closes or when desktop mode session ends.
+ * @property visibleTasks task ids for active freeform tasks that are currently visible. There
+ * might be other active tasks in desktop mode that are not visible.
+ * @property minimizedTasks task ids for active freeform tasks that are currently minimized.
+ * @property closingTasks task ids for tasks that are going to close, but are currently visible.
+ * @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom
+ * (top is at index 0).
+ */
+ private data class DesktopTaskData(
val activeTasks: ArraySet<Int> = ArraySet(),
val visibleTasks: ArraySet<Int> = ArraySet(),
val minimizedTasks: ArraySet<Int> = ArraySet(),
- // Tasks that are closing, but are still visible
// TODO(b/332682201): Remove when the repository state is updated via TransitionObserver
val closingTasks: ArraySet<Int> = ArraySet(),
- // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
)
- // Token of the current wallpaper activity, used to remove it when the last task is removed
+ /* Current wallpaper activity token to remove wallpaper activity when last task is removed. */
var wallpaperActivityToken: WindowContainerToken? = null
+
private val activeTasksListeners = ArraySet<ActiveTasksListener>()
- // Track visible tasks separately because a task may be part of the desktop but not visible.
private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
- // Track corner/caption regions of desktop tasks, used to determine gesture exclusion
+
+ /* Tracks corner/caption regions of desktop tasks, used to determine gesture exclusion. */
private val desktopExclusionRegions = SparseArray<Region>()
- // Track last bounds of task before toggled to stable bounds
+
+ /* Tracks last bounds of task before toggled to stable bounds. */
private val boundsBeforeMaximizeByTaskId = SparseArray<Rect>()
+
private var desktopGestureExclusionListener: Consumer<Region>? = null
private var desktopGestureExclusionExecutor: Executor? = null
- private val displayData =
- object : SparseArray<DisplayData>() {
- /**
- * Get the [DisplayData] associated with this [displayId]
- *
- * Creates a new instance if one does not exist
- */
- fun getOrCreate(displayId: Int): DisplayData {
- if (!contains(displayId)) {
- put(displayId, DisplayData())
- }
- return get(displayId)
- }
- }
+ private val desktopTaskDataByDisplayId = object : SparseArray<DesktopTaskData>() {
+ /** Gets [DesktopTaskData] for existing [displayId] or creates a new one. */
+ fun getOrCreate(displayId: Int): DesktopTaskData =
+ this[displayId] ?: DesktopTaskData().also { this[displayId] = it }
+ }
- /** Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository. */
+ /** Adds [activeTasksListener] to be notified of updates to active tasks. */
fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) {
activeTasksListeners.add(activeTasksListener)
}
- /** Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not. */
+ /** Adds [visibleTasksListener] to be notified of updates to visible tasks. */
fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
visibleTasksListeners[visibleTasksListener] = executor
- displayData.keyIterator().forEach {
+ desktopTaskDataByDisplayId.keyIterator().forEach {
+ val visibleTaskCount = getVisibleTaskCount(it)
executor.execute {
- visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount(it))
+ visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount)
}
}
}
- /** Returns a list of all [DisplayData]. */
- private fun displayDataList(): Sequence<DisplayData> =
- displayData.valueIterator().asSequence()
+ /** Updates tasks changes on all the active task listeners for given display id. */
+ private fun updateActiveTasksListeners(displayId: Int) {
+ activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
+ }
- /**
- * Add a Consumer which will inform other classes of changes to exclusion regions for all
- * Desktop tasks.
- */
+ /** Returns a list of all [DesktopTaskData] in the repository. */
+ private fun desktopTaskDataSequence(): Sequence<DesktopTaskData> =
+ desktopTaskDataByDisplayId.valueIterator().asSequence()
+
+ /** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */
fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
desktopGestureExclusionListener = regionListener
desktopGestureExclusionExecutor = executor
@@ -111,7 +111,7 @@
}
}
- /** Create a new merged region representative of all exclusion regions in all desktop tasks. */
+ /** Creates a new merged region representative of all exclusion regions in all desktop tasks. */
private fun calculateDesktopExclusionRegion(): Region {
val desktopExclusionRegion = Region()
desktopExclusionRegions.valueIterator().forEach { taskExclusionRegion ->
@@ -120,192 +120,120 @@
return desktopExclusionRegion
}
- /** Remove a previously registered [ActiveTasksListener] */
+ /** Remove the previously registered [activeTasksListener] */
fun removeActiveTasksListener(activeTasksListener: ActiveTasksListener) {
activeTasksListeners.remove(activeTasksListener)
}
- /** Remove a previously registered [VisibleTasksListener] */
+ /** Removes the previously registered [visibleTasksListener]. */
fun removeVisibleTasksListener(visibleTasksListener: VisibleTasksListener) {
visibleTasksListeners.remove(visibleTasksListener)
}
- /**
- * Mark a task with given [taskId] as active on given [displayId]
- *
- * @return `true` if the task was not active on given [displayId]
- */
- fun addActiveTask(displayId: Int, taskId: Int): Boolean {
- // Check if task is active on another display, if so, remove it
- displayData.forEach { id, data ->
- if (id != displayId && data.activeTasks.remove(taskId)) {
- activeTasksListeners.onEach { it.onActiveTasksChanged(id) }
+ /** Adds task with [taskId] to the list of active tasks on [displayId]. */
+ fun addActiveTask(displayId: Int, taskId: Int) {
+ // Removes task if it is active on another display excluding [displayId].
+ removeActiveTask(taskId, excludedDisplayId = displayId)
+
+ if (desktopTaskDataByDisplayId.getOrCreate(displayId).activeTasks.add(taskId)) {
+ logD("Adds active task=%d displayId=%d", taskId, displayId)
+ updateActiveTasksListeners(displayId)
+ }
+ }
+
+ /** Removes task from active task list of displays excluding the [excludedDisplayId]. */
+ fun removeActiveTask(taskId: Int, excludedDisplayId: Int? = null) {
+ desktopTaskDataByDisplayId.forEach { displayId, desktopTaskData ->
+ if ((displayId != excludedDisplayId)
+ && desktopTaskData.activeTasks.remove(taskId)) {
+ logD("Removed active task=%d displayId=%d", taskId, displayId)
+ updateActiveTasksListeners(displayId)
}
}
-
- val added = displayData.getOrCreate(displayId).activeTasks.add(taskId)
- if (added) {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: add active task=%d displayId=%d",
- taskId,
- displayId
- )
- activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
- }
- return added
}
- /**
- * Remove task with given [taskId] from active tasks.
- *
- * @return `true` if the task was active
- */
- fun removeActiveTask(taskId: Int): Boolean {
- var result = false
- displayData.forEach { displayId, data ->
- if (data.activeTasks.remove(taskId)) {
- activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
- result = true
+ /** Adds given task to the closing task list for [displayId]. */
+ fun addClosingTask(displayId: Int, taskId: Int) {
+ if (desktopTaskDataByDisplayId.getOrCreate(displayId).closingTasks.add(taskId)) {
+ logD("Added closing task=%d displayId=%d", taskId, displayId)
+ } else {
+ // If the task hasn't been removed from closing list after it disappeared.
+ logW("Task with taskId=%d displayId=%d is already closing", taskId, displayId)
+ }
+ }
+
+ /** Removes task from the list of closing tasks for [displayId]. */
+ fun removeClosingTask(taskId: Int) {
+ desktopTaskDataByDisplayId.forEach { displayId, taskInfo ->
+ if (taskInfo.closingTasks.remove(taskId)) {
+ logD("Removed closing task=%d displayId=%d", taskId, displayId)
}
}
- if (result) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove active task=%d", taskId)
- }
- return result
}
- /**
- * Mark a task with given [taskId] as closing on given [displayId]
- *
- * @return `true` if the task was not closing on given [displayId]
- */
- fun addClosingTask(displayId: Int, taskId: Int): Boolean {
- val added = displayData.getOrCreate(displayId).closingTasks.add(taskId)
- if (added) {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: added closing task=%d displayId=%d",
- taskId,
- displayId
- )
- }
- return added
- }
+ fun isActiveTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.activeTasks }
+ fun isClosingTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.closingTasks }
+ fun isVisibleTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.visibleTasks }
+ fun isMinimizedTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.minimizedTasks }
- /**
- * Remove task with given [taskId] from closing tasks.
- *
- * @return `true` if the task was closing
- */
- fun removeClosingTask(taskId: Int): Boolean {
- var removed = false
- displayData.forEach { _, data ->
- if (data.closingTasks.remove(taskId)) {
- removed = true
- }
- }
- if (removed) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove closing task=%d", taskId)
- }
- return removed
- }
-
- fun isActiveTask(taskId: Int) = displayDataList().any { taskId in it.activeTasks }
- fun isClosingTask(taskId: Int) = displayDataList().any { taskId in it.closingTasks }
- fun isVisibleTask(taskId: Int) = displayDataList().any { taskId in it.visibleTasks }
- fun isMinimizedTask(taskId: Int) = displayDataList().any { taskId in it.minimizedTasks }
-
- /**
- * Check if a task with the given [taskId] is the only visible, non-closing, not-minimized task
- * on its display
- */
+ /** Checks if a task is the only visible, non-closing, non-minimized task on its display. */
fun isOnlyVisibleNonClosingTask(taskId: Int): Boolean =
- displayDataList().any { data ->
- data.visibleTasks
- .subtract(data.closingTasks)
- .subtract(data.minimizedTasks)
- .singleOrNull() == taskId
+ desktopTaskDataSequence().any { it.visibleTasks
+ .subtract(it.closingTasks)
+ .subtract(it.minimizedTasks)
+ .singleOrNull() == taskId
}
- /** Get a set of the active tasks for given [displayId] */
- fun getActiveTasks(displayId: Int): ArraySet<Int> {
- return ArraySet(displayData[displayId]?.activeTasks)
- }
+ fun getActiveTasks(displayId: Int): ArraySet<Int> =
+ ArraySet(desktopTaskDataByDisplayId[displayId]?.activeTasks)
- /** Returns the minimized tasks for the given [displayId]. */
fun getMinimizedTasks(displayId: Int): ArraySet<Int> =
- ArraySet(displayData[displayId]?.minimizedTasks)
+ ArraySet(desktopTaskDataByDisplayId[displayId]?.minimizedTasks)
- /**
- * Returns a list of Tasks IDs representing all active non-minimized Tasks on the given display,
- * ordered from front to back.
- */
- fun getActiveNonMinimizedTasksOrderedFrontToBack(displayId: Int): List<Int> {
- val activeTasks = getActiveTasks(displayId)
- val allTasksInZOrder = getFreeformTasksInZOrder(displayId)
- return activeTasks
- // Don't show already minimized Tasks
- .filter { taskId -> !isMinimizedTask(taskId) }
- .sortedBy { taskId -> allTasksInZOrder.indexOf(taskId) }
+ /** Returns all active non-minimized tasks for [displayId] ordered from top to bottom. */
+ fun getActiveNonMinimizedOrderedTasks(displayId: Int): List<Int> =
+ getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) }
+
+ /** Returns a list of freeform tasks, ordered from top-bottom (top at index 0). */
+ fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> =
+ ArrayList(desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder ?: emptyList())
+
+ /** Removes task from visible tasks of all displays except [excludedDisplayId]. */
+ private fun removeVisibleTask(taskId: Int, excludedDisplayId: Int? = null) {
+ desktopTaskDataByDisplayId.forEach { displayId, data ->
+ if ((displayId != excludedDisplayId) && data.visibleTasks.remove(taskId)) {
+ notifyVisibleTaskListeners(displayId, data.visibleTasks.size)
+ }
+ }
}
- /** Get a list of freeform tasks, ordered from top-bottom (top at index 0). */
- fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> =
- ArrayList(displayData[displayId]?.freeformTasksInZOrder ?: emptyList())
-
/**
- * Updates whether a freeform task with this id is visible or not and notifies listeners.
+ * Updates visibility of a freeform task with [taskId] on [displayId] and notifies listeners.
*
- * If the task was visible on a different display with a different displayId, it is removed from
- * the set of visible tasks on that display. Listeners will be notified.
+ * If task was visible on a different display with a different [displayId], removes from
+ * the set of visible tasks on that display and notifies listeners.
*/
fun updateVisibleFreeformTasks(displayId: Int, taskId: Int, visible: Boolean) {
if (visible) {
- // Task is visible. Check if we need to remove it from any other display.
- val otherDisplays = displayData.keyIterator().asSequence().filter { it != displayId }
- for (otherDisplayId in otherDisplays) {
- if (displayData[otherDisplayId].visibleTasks.remove(taskId)) {
- notifyVisibleTaskListeners(
- otherDisplayId,
- displayData[otherDisplayId].visibleTasks.size
- )
- }
- }
+ // If task is visible, remove it from any other display besides [displayId].
+ removeVisibleTask(taskId, excludedDisplayId = displayId)
} else if (displayId == INVALID_DISPLAY) {
// Task has vanished. Check which display to remove the task from.
- displayData.forEach { displayId, data ->
- if (data.visibleTasks.remove(taskId)) {
- notifyVisibleTaskListeners(displayId, data.visibleTasks.size)
- }
- }
+ removeVisibleTask(taskId)
return
}
-
- val prevCount = visibleTaskCount(displayId)
+ val prevCount = getVisibleTaskCount(displayId)
if (visible) {
- displayData.getOrCreate(displayId).visibleTasks.add(taskId)
+ desktopTaskDataByDisplayId.getOrCreate(displayId).visibleTasks.add(taskId)
unminimizeTask(displayId, taskId)
} else {
- displayData[displayId]?.visibleTasks?.remove(taskId)
+ desktopTaskDataByDisplayId[displayId]?.visibleTasks?.remove(taskId)
}
- val newCount = visibleTaskCount(displayId)
-
- // Check if count changed
+ val newCount = getVisibleTaskCount(displayId)
if (prevCount != newCount) {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: update task visibility taskId=%d visible=%b displayId=%d",
- taskId,
- visible,
- displayId
- )
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: visibleTaskCount has changed from %d to %d",
- prevCount,
- newCount
- )
+ logD("Update task visibility taskId=%d visible=%b displayId=%d",
+ taskId, visible, displayId)
+ logD("VisibleTaskCount has changed from %d to %d", prevCount, newCount)
notifyVisibleTaskListeners(displayId, newCount)
}
}
@@ -316,72 +244,46 @@
}
}
- /** Get number of tasks that are marked as visible on given [displayId] */
- fun visibleTaskCount(displayId: Int): Int {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: visibleTaskCount= %d",
- displayData[displayId]?.visibleTasks?.size ?: 0
- )
- return displayData[displayId]?.visibleTasks?.size ?: 0
- }
+ /** Gets number of visible tasks on given [displayId] */
+ fun getVisibleTaskCount(displayId: Int): Int =
+ desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size ?: 0.also {
+ logD("getVisibleTaskCount=$it")
+ }
- /** Add (or move if it already exists) the task to the top of the ordered list. */
- // TODO(b/342417921): Identify if there is additional checks needed to move tasks for
- // multi-display scenarios.
+ /** Adds task (or moves if it already exists) to the top of the ordered list. */
fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: add or move task to top: display=%d, taskId=%d",
- displayId,
- taskId
- )
- displayData[displayId]?.freeformTasksInZOrder?.remove(taskId)
- displayData.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
+ logD("Add or move task to top: display=%d taskId=%d", taskId, displayId)
+ desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
+ desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
}
- /** Mark a Task as minimized. */
+ /** Minimizes the task for [taskId] and [displayId] */
fun minimizeTask(displayId: Int, taskId: Int) {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopModeTaskRepository: minimize Task: display=%d, task=%d",
- displayId,
- taskId
- )
- displayData.getOrCreate(displayId).minimizedTasks.add(taskId)
+ logD("Minimize Task: display=%d, task=%d", displayId, taskId)
+ desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
}
- /** Mark a Task as non-minimized. */
+ /** Unminimizes the task for [taskId] and [displayId] */
fun unminimizeTask(displayId: Int, taskId: Int) {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopModeTaskRepository: unminimize Task: display=%d, task=%d",
- displayId,
- taskId
- )
- displayData[displayId]?.minimizedTasks?.remove(taskId)
+ logD("Unminimize Task: display=%d, task=%d", displayId, taskId)
+ desktopTaskDataByDisplayId[displayId]?.minimizedTasks?.remove(taskId) ?:
+ logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)
}
- /** Remove the task from the ordered list. */
+ /** Removes task from the ordered list. */
fun removeFreeformTask(displayId: Int, taskId: Int) {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: remove freeform task from ordered list: display=%d, taskId=%d",
- displayId,
- taskId
- )
- displayData[displayId]?.freeformTasksInZOrder?.remove(taskId)
+ logD("Removes freeform task: taskId=%d", taskId)
+ desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
boundsBeforeMaximizeByTaskId.remove(taskId)
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: remaining freeform tasks: %s",
- displayData[displayId]?.freeformTasksInZOrder?.toDumpString() ?: ""
- )
+ logD("Remaining freeform tasks: %d",
+ desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString() ?: "")
}
/**
- * Updates the active desktop gesture exclusion regions; if desktopExclusionRegions has been
- * accepted by desktopGestureExclusionListener, it will be updated in the appropriate classes.
+ * Updates active desktop gesture exclusion regions.
+ *
+ * If [desktopExclusionRegions] is accepted by [desktopGestureExclusionListener], updates it in
+ * appropriate classes.
*/
fun updateTaskExclusionRegions(taskId: Int, taskExclusionRegions: Region) {
desktopExclusionRegions.put(taskId, taskExclusionRegions)
@@ -391,9 +293,10 @@
}
/**
- * Removes the desktop gesture exclusion region for the specified task; if exclusionRegion has
- * been accepted by desktopGestureExclusionListener, it will be updated in the appropriate
- * classes.
+ * Removes desktop gesture exclusion region for the specified task.
+ *
+ * If [desktopExclusionRegions] is accepted by [desktopGestureExclusionListener], updates it in
+ * appropriate classes.
*/
fun removeExclusionRegion(taskId: Int) {
desktopExclusionRegions.delete(taskId)
@@ -403,26 +306,24 @@
}
/** Removes and returns the bounds saved before maximizing the given task. */
- fun removeBoundsBeforeMaximize(taskId: Int): Rect? {
- return boundsBeforeMaximizeByTaskId.removeReturnOld(taskId)
- }
+ fun removeBoundsBeforeMaximize(taskId: Int): Rect? =
+ boundsBeforeMaximizeByTaskId.removeReturnOld(taskId)
/** Saves the bounds of the given task before maximizing. */
- fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) {
+ fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) =
boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
- }
internal fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopModeTaskRepository")
- dumpDisplayData(pw, innerPrefix)
+ dumpDesktopTaskData(pw, innerPrefix)
pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}")
pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}")
}
- private fun dumpDisplayData(pw: PrintWriter, prefix: String) {
+ private fun dumpDesktopTaskData(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
- displayData.forEach { displayId, data ->
+ desktopTaskDataByDisplayId.forEach { displayId, data ->
pw.println("${prefix}Display $displayId:")
pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}")
pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}")
@@ -432,23 +333,28 @@
}
}
- /**
- * Defines interface for classes that can listen to changes for active tasks in desktop mode.
- */
+ /** Listens to changes for active tasks in desktop mode. */
interface ActiveTasksListener {
- /** Called when the active tasks change in desktop mode. */
fun onActiveTasksChanged(displayId: Int) {}
}
- /**
- * Defines interface for classes that can listen to changes for visible tasks in desktop mode.
- */
+ /** Listens to changes for visible tasks in desktop mode. */
interface VisibleTasksListener {
- /** Called when the desktop changes the number of visible freeform tasks. */
fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {}
}
+
+ private fun logD(msg: String, vararg arguments: Any?) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ private fun logW(msg: String, vararg arguments: Any?) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ companion object {
+ private const val TAG = "DesktopModeTaskRepository"
+ }
}
-private fun <T> Iterable<T>.toDumpString(): String {
- return joinToString(separator = ", ", prefix = "[", postfix = "]")
-}
+private fun <T> Iterable<T>.toDumpString(): String =
+ joinToString(separator = ", ", prefix = "[", postfix = "]")
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..9d3b2e5 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
@@ -254,7 +255,7 @@
/** Gets number of visible tasks in [displayId]. */
fun visibleTaskCount(displayId: Int): Int =
- desktopModeTaskRepository.visibleTaskCount(displayId)
+ desktopModeTaskRepository.getVisibleTaskCount(displayId)
/** Returns true if any tasks are visible in Desktop Mode. */
fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0
@@ -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,
@@ -445,14 +446,7 @@
if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskId)) {
removeWallpaperActivity(wct)
}
- if (!desktopModeTaskRepository.addClosingTask(displayId, taskId)) {
- // Could happen if the task hasn't been removed from closing list after it disappeared
- ProtoLog.w(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: the task with taskId=%d is already closing!",
- taskId
- )
- }
+ desktopModeTaskRepository.addClosingTask(displayId, taskId)
}
/** Move a task with given `taskId` to fullscreen */
@@ -663,7 +657,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))
@@ -793,7 +787,7 @@
}
val nonMinimizedTasksOrderedFrontToBack =
- desktopModeTaskRepository.getActiveNonMinimizedTasksOrderedFrontToBack(displayId)
+ desktopModeTaskRepository.getActiveNonMinimizedOrderedTasks(displayId)
// If we're adding a new Task we might need to minimize an old one
val taskToMinimize: RunningTaskInfo? =
if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) {
@@ -811,7 +805,7 @@
.filter { taskId -> taskId != taskToMinimize?.taskId }
.mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
.reversed() // Start from the back so the front task is brought forward last
- .forEach { task -> wct.reorder(task.token, true /* onTop */) }
+ .forEach { task -> wct.reorder(task.token, /* onTop= */ true) }
return taskToMinimize
}
@@ -819,7 +813,7 @@
shellTaskOrganizer
.getRunningTasks(context.displayId)
.firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME }
- ?.let { homeTask -> wct.reorder(homeTask.getToken(), toTop /* onTop */) }
+ ?.let { homeTask -> wct.reorder(homeTask.getToken(), /* onTop= */ toTop) }
}
private fun addWallpaperActivity(wct: WindowContainerTransaction) {
@@ -971,7 +965,7 @@
}
private fun isIncompatibleTask(task: TaskInfo) =
- Flags.enableDesktopWindowingModalsPolicy()
+ DesktopModeFlags.MODALS_POLICY.isEnabled(context)
&& isTopActivityExemptFromDesktopWindowing(context, task)
private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean {
@@ -1068,14 +1062,7 @@
// Remove wallpaper activity when the last active task is removed
removeWallpaperActivity(wct)
}
- if (!desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)) {
- // Could happen if the task hasn't been removed from closing list after it disappeared
- ProtoLog.w(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: the task with taskId=%d is already closing!",
- task.taskId
- )
- }
+ desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)
// If a CLOSE or TO_BACK is triggered on a desktop task, remove the task.
if (Flags.enableDesktopWindowingBackNavigation() &&
desktopModeTaskRepository.isVisibleTask(task.taskId)) {
@@ -1361,7 +1348,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/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index c5ed1be..a011ff5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -65,10 +65,10 @@
}
override fun onTransitionReady(
- transition: IBinder,
- info: TransitionInfo,
- startTransaction: SurfaceControl.Transaction,
- finishTransaction: SurfaceControl.Transaction
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction
) {
val taskToMinimize = mPendingTransitionTokensAndTasks.remove(transition) ?: return
@@ -129,8 +129,7 @@
}
fun removeLeftoverMinimizedTasks(displayId: Int, wct: WindowContainerTransaction) {
- if (taskRepository
- .getActiveNonMinimizedTasksOrderedFrontToBack(displayId).isNotEmpty()) {
+ if (taskRepository.getActiveNonMinimizedOrderedTasks(displayId).isNotEmpty()) {
return
}
val remainingMinimizedTasks = taskRepository.getMinimizedTasks(displayId)
@@ -178,7 +177,7 @@
"DesktopTasksLimiter: addMinimizeBackTaskChangesIfNeeded, newFrontTask=%d",
newFrontTaskInfo.taskId)
val newTaskListOrderedFrontToBack = createOrderedTaskListWithGivenTaskInFront(
- taskRepository.getActiveNonMinimizedTasksOrderedFrontToBack(displayId),
+ taskRepository.getActiveNonMinimizedOrderedTasks(displayId),
newFrontTaskInfo.taskId)
val taskToMinimize = getTaskToMinimizeIfNeeded(newTaskListOrderedFrontToBack)
if (taskToMinimize != null) {
@@ -242,7 +241,5 @@
}
@VisibleForTesting
- fun getTransitionObserver(): TransitionObserver {
- return minimizeTransitionObserver
- }
+ fun getTransitionObserver(): TransitionObserver = minimizeTransitionObserver
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index e4aa115..d03a561 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -21,11 +21,11 @@
import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
@@ -40,6 +40,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
@@ -290,7 +291,7 @@
final int activityType = taskInfo1.getActivityType();
if (activityType == ACTIVITY_TYPE_STANDARD) {
Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
- int bgColor1 = getResizingBackgroundColor(taskInfo1).toArgb();
+ int bgColor1 = getResizingBackgroundColor(taskInfo1);
mDropZoneView1.setAppInfo(bgColor1, icon1);
mDropZoneView2.setAppInfo(bgColor1, icon1);
mDropZoneView1.setForceIgnoreBottomMargin(false);
@@ -312,10 +313,10 @@
mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
if (topOrLeftTask != null && bottomOrRightTask != null) {
Drawable topOrLeftIcon = mIconProvider.getIcon(topOrLeftTask.topActivityInfo);
- int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask).toArgb();
+ int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask);
Drawable bottomOrRightIcon = mIconProvider.getIcon(
bottomOrRightTask.topActivityInfo);
- int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask).toArgb();
+ int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask);
mDropZoneView1.setAppInfo(topOrLeftColor, topOrLeftIcon);
mDropZoneView2.setAppInfo(bottomOrRightColor, bottomOrRightIcon);
}
@@ -586,6 +587,11 @@
}
}
+ private static int getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
+ final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
+ return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
+ }
+
/**
* Dumps information about this drag layout.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 4531967..229d972 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -101,12 +101,9 @@
repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId);
if (taskInfo.isVisible) {
- if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
- }
+ repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId,
- true);
+ true);
}
});
}
@@ -122,10 +119,7 @@
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId);
repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId);
- if (repository.removeActiveTask(taskInfo.taskId)) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Removing active freeform task: #%d", taskInfo.taskId);
- }
+ repository.removeActiveTask(taskInfo.taskId, /* excludedDisplayId= */ null);
repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, false);
});
}
@@ -146,14 +140,9 @@
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
mDesktopModeTaskRepository.ifPresent(repository -> {
if (taskInfo.isVisible) {
- if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
- }
- } else if (repository.isClosingTask(taskInfo.taskId)
- && repository.removeClosingTask(taskInfo.taskId)) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Removing closing freeform task: #%d", taskInfo.taskId);
+ repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
+ } else if (repository.isClosingTask(taskInfo.taskId)) {
+ repository.removeClosingTask(taskInfo.taskId);
}
repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId,
taskInfo.isVisible);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index a52141c5..ba97c832 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -21,6 +21,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.util.RotationUtils.deltaRotation;
import static android.util.RotationUtils.rotateBounds;
+import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -1183,10 +1184,15 @@
? pipTaskInfo.configuration.windowConfiguration.getBounds()
: mPipOrganizer.mAppBounds;
+ // Populate the final surface control transactions from PipTransitionAnimator,
+ // display cutout insets is handled in the swipe pip to home animator, empty it out here
+ // to avoid flicker.
+ final Rect savedDisplayCutoutInsets = new Rect(pipTaskInfo.displayCutoutInsets);
+ pipTaskInfo.displayCutoutInsets.setEmpty();
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
- 0 /* startingAngle */, 0 /* rotationDelta */)
+ 0 /* startingAngle */, ROTATION_0 /* rotationDelta */)
.setPipTransactionHandler(mTransactionConsumer)
.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP);
// The start state is the end state for swipe-auto-pip.
@@ -1194,6 +1200,7 @@
animator.applySurfaceControlTransaction(leash, startTransaction,
PipAnimationController.FRACTION_END);
startTransaction.apply();
+ pipTaskInfo.displayCutoutInsets.set(savedDisplayCutoutInsets);
mPipBoundsState.setBounds(destinationBounds);
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index b939b16..8aa0933 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -44,6 +44,7 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.ImeListener;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -56,7 +57,6 @@
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -201,6 +201,11 @@
.getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
}
});
+ mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
+ new ImeListener(mDisplayController, mPipDisplayLayoutState.getDisplayId()) {
+ @Override
+ public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
+ });
// Allow other outside processes to bind to PiP controller using the key below.
mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
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/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index d7ee563..2531ff1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -41,7 +41,6 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
-import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
@@ -2458,13 +2457,8 @@
updateSurfaceBounds(layout, t, shouldUseParallaxEffect);
getMainStageBounds(mTempRect1);
getSideStageBounds(mTempRect2);
- // TODO (b/307490004): "commonColor" below is a temporary fix to ensure the colors on both
- // sides match. When b/307490004 is fixed, this code can be reverted.
- float[] commonColor = getResizingBackgroundColor(mSideStage.mRootTaskInfo).getComponents();
- mMainStage.onResizing(
- mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately, commonColor);
- mSideStage.onResizing(
- mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately, commonColor);
+ mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
+ mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
t.apply();
mTransactionPool.release(t);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 1076eca..d1ab3e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -314,10 +314,10 @@
}
void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
- int offsetY, boolean immediately, float[] veilColor) {
+ int offsetY, boolean immediately) {
if (mSplitDecorManager != null && mRootTaskInfo != null) {
mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX,
- offsetY, immediately, veilColor);
+ offsetY, immediately);
}
}
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..0ff3522 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
@@ -30,6 +30,7 @@
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
import static android.view.MotionEvent.ACTION_HOVER_EXIT;
import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_OUTSIDE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
@@ -101,6 +102,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;
@@ -501,8 +503,6 @@
if (!decoration.isHandleMenuActive()) {
moveTaskToFront(decoration.mTaskInfo);
decoration.createHandleMenu(mSplitScreenController);
- } else {
- decoration.closeHandleMenu();
}
} else if (id == R.id.desktop_button) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -548,6 +548,17 @@
@Override
public boolean onTouch(View v, MotionEvent e) {
final int id = v.getId();
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ if (e.getActionMasked() == ACTION_OUTSIDE) {
+ if (id == R.id.handle_menu) {
+ // Close handle menu on outside touch if menu is directly touchable; if not,
+ // it will be handled by handleEventOutsideCaption.
+ if (decoration.mTaskInfo.isFreeform()
+ || Flags.enableAdditionalWindowsAboveStatusBar()) {
+ decoration.closeHandleMenu();
+ }
+ }
+ }
if ((e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN) {
mTouchscreenInUse = e.getActionMasked() != ACTION_UP
&& e.getActionMasked() != ACTION_CANCEL;
@@ -557,7 +568,6 @@
&& id != R.id.maximize_window) {
return false;
}
- final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
moveTaskToFront(decoration.mTaskInfo);
final int actionMasked = e.getActionMasked();
@@ -586,7 +596,6 @@
mShouldPilferCaptionEvents = !(downInCustomizableCaptionRegion
&& downInExclusionRegion && isTransparentCaption) && !isResizeEvent;
}
-
if (!mShouldPilferCaptionEvents) {
// The event will be handled by a window below or pilfered by resize handler.
return false;
@@ -600,9 +609,6 @@
// Gesture is finished, reset state.
mShouldPilferCaptionEvents = false;
}
- if (!mHasLongClicked && id != R.id.maximize_window) {
- decoration.closeMaximizeMenuIfNeeded(e);
- }
return mDragDetector.onMotionEvent(v, e);
}
@@ -895,10 +901,6 @@
*
* @param relevantDecor the window decoration of the focused task's caption. This method only
* handles motion events outside this caption's bounds.
- * TODO(b/349135068): Outside-touch detection no longer works with the
- * enableAdditionalWindowsAboveStatusBar flag enabled. This
- * will be fixed once we can add FLAG_WATCH_OUTSIDE_TOUCH to relevant menus,
- * at which point, all EventReceivers and external touch logic should be removed.
*/
private void handleEventOutsideCaption(MotionEvent ev,
DesktopModeWindowDecoration relevantDecor) {
@@ -909,9 +911,8 @@
relevantDecor.updateHoverAndPressStatus(ev);
final int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- if (!mTransitionDragActive) {
+ if (!mTransitionDragActive && !Flags.enableAdditionalWindowsAboveStatusBar()) {
relevantDecor.closeHandleMenuIfNeeded(ev);
- relevantDecor.closeMaximizeMenuIfNeeded(ev);
}
}
}
@@ -1124,7 +1125,7 @@
&& taskInfo.isFocused) {
return false;
}
- if (Flags.enableDesktopWindowingModalsPolicy()
+ if (DesktopModeFlags.MODALS_POLICY.isEnabled(mContext)
&& isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) {
return false;
}
@@ -1249,7 +1250,6 @@
}
}
-
private class DragStartListenerImpl
implements DragPositioningCallbackUtility.DragStartListener {
@Override
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..41f428b 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
@@ -19,6 +19,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.windowingModeToString;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
@@ -75,6 +77,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;
@@ -592,6 +595,17 @@
// through to the windows below so that the app can respond to input events on
// their custom content.
relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ } else {
+ if (Flags.enableCaptionCompatInsetForceConsumption()) {
+ // Force-consume the caption bar insets when the app tries to hide the caption.
+ // This improves app compatibility of immersive apps.
+ relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING;
+ }
+ }
+ if (Flags.enableCaptionCompatInsetForceConsumptionAlways()) {
+ // Always force-consume the caption bar insets for maximum app compatibility,
+ // including non-immersive apps that just don't handle caption insets properly.
+ relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
}
// Report occluding elements as bounding rects to the insets system so that apps can
// draw in the empty space in the center:
@@ -630,7 +644,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);
@@ -724,7 +738,11 @@
return;
}
final PackageManager pm = mContext.getApplicationContext().getPackageManager();
- final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */);
+ final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity,
+ // Include uninstalled apps. Despite its name, adding this flag is a workaround
+ // to #getActivityInfo throwing a NameNotFoundException for installed packages
+ // when HSUM is enabled. See b/354884302.
+ PackageManager.MATCH_UNINSTALLED_PACKAGES);
final IconProvider provider = new IconProvider(mContext);
final Drawable appIconDrawable = provider.getIcon(activityInfo);
final BaseIconFactory headerIconFactory = createIconFactory(mContext,
@@ -890,6 +908,10 @@
mIsMaximizeMenuHovered = hovered;
onMaximizeHoverStateChanged();
return null;
+ },
+ () -> {
+ closeMaximizeMenu();
+ return null;
}
);
}
@@ -1100,8 +1122,13 @@
handle.performClick();
}
if (isHandleMenuActive()) {
- mHandleMenu.checkMotionEvent(ev);
- closeHandleMenuIfNeeded(ev);
+ // If the whole handle menu can be touched directly, rely on FLAG_WATCH_OUTSIDE_TOUCH.
+ // This is for the case that some of the handle menu is underneath the status bar.
+ if (isAppHandle(mWindowDecorViewHolder)
+ && !Flags.enableAdditionalWindowsAboveStatusBar()) {
+ mHandleMenu.checkMotionEvent(ev);
+ closeHandleMenuIfNeeded(ev);
+ }
}
}
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/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 32522c6..7a81d4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -31,6 +31,7 @@
import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.View
+import android.view.WindowManager
import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView
@@ -140,7 +141,9 @@
x = x,
y = y,
width = menuWidth,
- height = menuHeight
+ height = menuHeight,
+ flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
)
} else {
parentDecor.addWindow(
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/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 5f9f8d6..aa2ce0f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -40,6 +40,7 @@
import android.view.MotionEvent.ACTION_HOVER_ENTER
import android.view.MotionEvent.ACTION_HOVER_EXIT
import android.view.MotionEvent.ACTION_HOVER_MOVE
+import android.view.MotionEvent.ACTION_OUTSIDE
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.SurfaceControlViewHost
@@ -104,14 +105,16 @@
onMaximizeClickListener: OnTaskActionClickListener,
onLeftSnapClickListener: OnTaskActionClickListener,
onRightSnapClickListener: OnTaskActionClickListener,
- onHoverListener: (Boolean) -> Unit
+ onHoverListener: (Boolean) -> Unit,
+ onOutsideTouchListener: () -> Unit,
) {
if (maximizeMenu != null) return
createMaximizeMenu(
onMaximizeClickListener = onMaximizeClickListener,
onLeftSnapClickListener = onLeftSnapClickListener,
onRightSnapClickListener = onRightSnapClickListener,
- onHoverListener = onHoverListener
+ onHoverListener = onHoverListener,
+ onOutsideTouchListener = onOutsideTouchListener
)
maximizeMenuView?.animateOpenMenu()
}
@@ -129,7 +132,8 @@
onMaximizeClickListener: OnTaskActionClickListener,
onLeftSnapClickListener: OnTaskActionClickListener,
onRightSnapClickListener: OnTaskActionClickListener,
- onHoverListener: (Boolean) -> Unit
+ onHoverListener: (Boolean) -> Unit,
+ onOutsideTouchListener: () -> Unit
) {
val t = transactionSupplier.get()
val builder = SurfaceControl.Builder()
@@ -142,7 +146,8 @@
menuWidth,
menuHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSPARENT
)
lp.title = "Maximize Menu for Task=" + taskInfo.taskId
@@ -172,6 +177,7 @@
onRightSnapClickListener.onClick(taskId, "right_snap_option")
}
menuView.onMenuHoverListener = onHoverListener
+ menuView.onOutsideTouchListener = onOutsideTouchListener
viewHost.setView(menuView.rootView, lp)
}
@@ -268,6 +274,8 @@
var onRightSnapClickListener: (() -> Unit)? = null
/** Invoked whenever the hover state of the menu changes. */
var onMenuHoverListener: ((Boolean) -> Unit)? = null
+ /** Invoked whenever a click occurs outside the menu */
+ var onOutsideTouchListener: (() -> Unit)? = null
init {
overlay.setOnHoverListener { _, event ->
@@ -312,6 +320,13 @@
maximizeButton.setOnClickListener { onMaximizeClickListener?.invoke() }
snapRightButton.setOnClickListener { onRightSnapClickListener?.invoke() }
snapLeftButton.setOnClickListener { onLeftSnapClickListener?.invoke() }
+ rootView.setOnTouchListener { _, event ->
+ if (event.actionMasked == ACTION_OUTSIDE) {
+ onOutsideTouchListener?.invoke()
+ false
+ }
+ true
+ }
// To prevent aliasing.
maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index a691f59..3b8657d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -19,10 +19,10 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
-import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import android.annotation.NonNull;
@@ -54,7 +54,6 @@
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -375,7 +374,8 @@
}
final WindowDecorationInsets newInsets = new WindowDecorationInsets(
- mTaskInfo.token, mOwner, captionInsetsRect, boundingRects);
+ mTaskInfo.token, mOwner, captionInsetsRect, boundingRects,
+ params.mInsetSourceFlags);
if (!newInsets.equals(mWindowDecorationInsets)) {
// Add or update this caption as an insets source.
mWindowDecorationInsets = newInsets;
@@ -635,7 +635,8 @@
.show(windowSurfaceControl);
final WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(width, height, TYPE_APPLICATION,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSPARENT);
lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
@@ -660,7 +661,7 @@
final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId);
final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
final WindowDecorationInsets newInsets = new WindowDecorationInsets(mTaskInfo.token,
- mOwner, captionInsets, null /* boundingRets */);
+ mOwner, captionInsets, null /* boundingRets */, 0 /* flags */);
if (!newInsets.equals(mWindowDecorationInsets)) {
mWindowDecorationInsets = newInsets;
mWindowDecorationInsets.addOrUpdate(wct);
@@ -674,6 +675,7 @@
int mCaptionWidthId;
final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>();
int mInputFeatures;
+ @InsetsSource.Flags int mInsetSourceFlags;
int mShadowRadiusId;
int mCornerRadius;
@@ -689,6 +691,7 @@
mCaptionWidthId = Resources.ID_NULL;
mOccludingCaptionElements.clear();
mInputFeatures = 0;
+ mInsetSourceFlags = 0;
mShadowRadiusId = Resources.ID_NULL;
mCornerRadius = 0;
@@ -753,20 +756,20 @@
private final Binder mOwner;
private final Rect mFrame;
private final Rect[] mBoundingRects;
+ private final @InsetsSource.Flags int mFlags;
private WindowDecorationInsets(WindowContainerToken token, Binder owner, Rect frame,
- Rect[] boundingRects) {
+ Rect[] boundingRects, @InsetsSource.Flags int flags) {
mToken = token;
mOwner = owner;
mFrame = frame;
mBoundingRects = boundingRects;
+ mFlags = flags;
}
void addOrUpdate(WindowContainerTransaction wct) {
- final @InsetsSource.Flags int captionSourceFlags =
- Flags.enableCaptionCompatInsetForceConsumption() ? FLAG_FORCE_CONSUMING : 0;
wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects,
- captionSourceFlags);
+ mFlags);
wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame,
mBoundingRects, 0 /* flags */);
}
@@ -782,12 +785,13 @@
if (!(o instanceof WindowDecoration.WindowDecorationInsets that)) return false;
return Objects.equals(mToken, that.mToken) && Objects.equals(mOwner,
that.mOwner) && Objects.equals(mFrame, that.mFrame)
- && Objects.deepEquals(mBoundingRects, that.mBoundingRects);
+ && Objects.deepEquals(mBoundingRects, that.mBoundingRects)
+ && mFlags == that.mFlags;
}
@Override
public int hashCode() {
- return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects));
+ return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects), mFlags);
}
}
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index 6a354f1..f1370bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -35,6 +35,7 @@
y: Int,
width: Int,
height: Int,
+ flags: Int,
layoutId: Int? = null
) : AdditionalViewContainer() {
override val view: View
@@ -49,7 +50,7 @@
val lp = WindowManager.LayoutParams(
width, height, x, y,
WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ flags,
PixelFormat.TRANSPARENT
).apply {
title = "Additional view container of Task=$taskId"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 57d8cac..753723c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -97,7 +97,8 @@
handleHeight: Int) {
if (!Flags.enableAdditionalWindowsAboveStatusBar()) return
statusBarInputLayer = AdditionalSystemViewContainer(context, taskInfo.taskId,
- handlePosition.x, handlePosition.y, handleWidth, handleHeight)
+ handlePosition.x, handlePosition.y, handleWidth, handleHeight,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
val view = statusBarInputLayer?.view
val lp = view?.layoutParams as WindowManager.LayoutParams
lp.title = "Handle Input Layer of task " + taskInfo.taskId
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/common/ImeListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/ImeListenerTest.kt
new file mode 100644
index 0000000..3b0a072
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/ImeListenerTest.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.wm.shell.common
+
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Insets
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.view.DisplayCutout
+import android.view.DisplayInfo
+import android.view.InsetsSource.ID_IME
+import android.view.InsetsState
+import android.view.Surface
+import android.view.WindowInsets.Type
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.wm.shell.ShellTestCase
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.kotlin.whenever
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ImeListenerTest : ShellTestCase() {
+ private lateinit var imeListener: CachingImeListener
+ private lateinit var displayLayout: DisplayLayout
+
+ @Mock private lateinit var displayController: DisplayController
+ @Before
+ fun setUp() {
+ val resources = createResources(40, 50, false)
+ val displayInfo = createDisplayInfo(1000, 1500, 0, Surface.ROTATION_0)
+ displayLayout = DisplayLayout(displayInfo, resources, false, false)
+ whenever(displayController.getDisplayLayout(DEFAULT_DISPLAY_ID)).thenReturn(displayLayout)
+ imeListener = CachingImeListener(displayController, DEFAULT_DISPLAY_ID)
+ }
+
+ @Test
+ fun testImeAppears() {
+ val insetsState = createInsetsStateWithIme(true, DEFAULT_IME_HEIGHT)
+ imeListener.insetsChanged(insetsState)
+ assertTrue("Ime insets source should become visible", imeListener.cachedImeVisible)
+ assertEquals(DEFAULT_IME_HEIGHT, imeListener.cachedImeHeight)
+ }
+
+ @Test
+ fun testImeAppears_thenDisappears() {
+ // Send insetsState with an IME as a visible source.
+ val insetsStateWithIme = createInsetsStateWithIme(true, DEFAULT_IME_HEIGHT)
+ imeListener.insetsChanged(insetsStateWithIme)
+
+ // Send insetsState without IME.
+ val insetsStateWithoutIme = createInsetsStateWithIme(false, 0)
+ imeListener.insetsChanged(insetsStateWithoutIme)
+
+ assertFalse("Ime insets source should become invisible",
+ imeListener.cachedImeVisible)
+ assertEquals(0, imeListener.cachedImeHeight)
+ }
+
+ private fun createInsetsStateWithIme(isVisible: Boolean, imeHeight: Int): InsetsState {
+ val stableBounds = Rect()
+ displayLayout.getStableBounds(stableBounds)
+ val insetsState = InsetsState()
+
+ val insetsSource = insetsState.getOrCreateSource(ID_IME, Type.ime())
+ insetsSource.setVisible(isVisible)
+ insetsSource.setFrame(stableBounds.left, stableBounds.bottom - imeHeight,
+ stableBounds.right, stableBounds.bottom)
+ return insetsState
+ }
+
+ private fun createDisplayInfo(width: Int, height: Int, cutoutHeight: Int,
+ rotation: Int): DisplayInfo {
+ val info = DisplayInfo()
+ info.logicalWidth = width
+ info.logicalHeight = height
+ info.rotation = rotation
+ if (cutoutHeight > 0) {
+ info.displayCutout = DisplayCutout(
+ Insets.of(0, cutoutHeight, 0, 0) /* safeInsets */,
+ null /* boundLeft */,
+ Rect(width / 2 - cutoutHeight, 0, width / 2 + cutoutHeight,
+ cutoutHeight) /* boundTop */, null /* boundRight */,
+ null /* boundBottom */)
+ } else {
+ info.displayCutout = DisplayCutout.NO_CUTOUT
+ }
+ info.logicalDensityDpi = 300
+ return info
+ }
+
+ private fun createResources(navLand: Int, navPort: Int, navMoves: Boolean): Resources {
+ val cfg = Configuration()
+ cfg.uiMode = Configuration.UI_MODE_TYPE_NORMAL
+ val res = Mockito.mock(Resources::class.java)
+ Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize(
+ R.dimen.navigation_bar_height_landscape_car_mode)
+ Mockito.doReturn(navPort).whenever(res).getDimensionPixelSize(
+ R.dimen.navigation_bar_height_car_mode)
+ Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize(
+ R.dimen.navigation_bar_width_car_mode)
+ Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize(
+ R.dimen.navigation_bar_height_landscape)
+ Mockito.doReturn(navPort).whenever(res).getDimensionPixelSize(
+ R.dimen.navigation_bar_height)
+ Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize(
+ R.dimen.navigation_bar_width)
+ Mockito.doReturn(navMoves).whenever(res).getBoolean(R.bool.config_navBarCanMove)
+ Mockito.doReturn(cfg).whenever(res).configuration
+ return res
+ }
+
+ private class CachingImeListener(
+ displayController: DisplayController,
+ displayId: Int
+ ) : ImeListener(displayController, displayId) {
+ var cachedImeVisible = false
+ var cachedImeHeight = 0
+ public override fun onImeVisibilityChanged(imeVisible: Boolean, imeHeight: Int) {
+ cachedImeVisible = imeVisible
+ cachedImeHeight = imeHeight
+ }
+ }
+
+ companion object {
+ private const val DEFAULT_DISPLAY_ID = 0
+ private const val DEFAULT_IME_HEIGHT = 500
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 18b08bf..0a5672d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -41,32 +41,44 @@
}
@Test
- fun addActiveTask_listenerNotifiedAndTaskIsActive() {
+ fun addActiveTask_notifiesListener() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+
assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1)
+ }
+
+ @Test
+ fun addActiveTask_taskIsActive() {
+ val listener = TestListener()
+ repo.addActiveTaskListener(listener)
+
+ repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+
assertThat(repo.isActiveTask(1)).isTrue()
}
@Test
- fun addActiveTask_sameTaskDoesNotNotify() {
+ fun addSameActiveTaskTwice_notifiesOnce() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+
assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1)
}
@Test
- fun addActiveTask_multipleTasksAddedNotifiesForEach() {
+ fun addActiveTask_multipleTasksAdded_notifiesForAllTasks() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
repo.addActiveTask(DEFAULT_DISPLAY, taskId = 2)
+
assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2)
}
@@ -84,22 +96,35 @@
}
@Test
- fun removeActiveTask_listenerNotifiedAndTaskNotActive() {
+ fun removeActiveTask_notifiesListener() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
-
repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+
repo.removeActiveTask(1)
+
// Notify once for add and once for remove
assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2)
+ }
+
+ @Test
+ fun removeActiveTask_taskNotActive() {
+ val listener = TestListener()
+ repo.addActiveTaskListener(listener)
+ repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+
+ repo.removeActiveTask(1)
+
assertThat(repo.isActiveTask(1)).isFalse()
}
@Test
- fun removeActiveTask_removeNotExistingTaskDoesNotNotify() {
+ fun removeActiveTask_nonExistingTask_doesNotNotify() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
+
repo.removeActiveTask(99)
+
assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(0)
}
@@ -108,32 +133,38 @@
val listener = TestListener()
repo.addActiveTaskListener(listener)
repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+
repo.removeActiveTask(1)
+
assertThat(listener.activeChangesOnSecondaryDisplay).isEqualTo(0)
assertThat(repo.isActiveTask(1)).isFalse()
}
@Test
- fun isActiveTask_notExistingTaskReturnsFalse() {
+ fun isActiveTask_nonExistingTask_returnsFalse() {
assertThat(repo.isActiveTask(99)).isFalse()
}
@Test
- fun isOnlyVisibleNonClosingTask_noTasks() {
+ fun isOnlyVisibleNonClosingTask_noTasks_returnsFalse() {
// No visible tasks
assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse()
+ }
+
+ @Test
+ fun isClosingTask_noTasks_returnsFalse() {
+ // No visible tasks
assertThat(repo.isClosingTask(1)).isFalse()
}
@Test
- fun isOnlyVisibleNonClosingTask_singleVisibleNonClosingTask() {
+ fun updateVisibleFreeformTasks_singleVisibleNonClosingTask_updatesTasksCorrectly() {
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- // The only visible task
assertThat(repo.isVisibleTask(1)).isTrue()
assertThat(repo.isClosingTask(1)).isFalse()
assertThat(repo.isOnlyVisibleNonClosingTask(1)).isTrue()
- // Not a visible task
+
assertThat(repo.isVisibleTask(99)).isFalse()
assertThat(repo.isClosingTask(99)).isFalse()
assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse()
@@ -207,10 +238,11 @@
}
@Test
- fun addListener_notifiesVisibleFreeformTask() {
+ fun addVisibleTasksListener_notifiesVisibleFreeformTask() {
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
+
repo.addVisibleTasksListener(listener, executor)
executor.flushAll()
@@ -236,6 +268,7 @@
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
repo.addVisibleTasksListener(listener, executor)
+
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
executor.flushAll()
@@ -303,6 +336,7 @@
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
+
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
executor.flushAll()
@@ -329,6 +363,7 @@
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
+
repo.updateVisibleFreeformTasks(INVALID_DISPLAY, taskId = 1, visible = false)
executor.flushAll()
@@ -337,65 +372,73 @@
}
@Test
- fun visibleTaskCount_defaultDisplay_returnsCorrectCount() {
+ fun getVisibleTaskCount_defaultDisplay_returnsCorrectCount() {
// No tasks, count is 0
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
// New task increments count to 1
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Visibility update to same task does not increase count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Second task visible increments count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
// Hiding a task decrements count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Hiding all tasks leaves count at 0
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false)
- assertThat(repo.visibleTaskCount(displayId = 9)).isEqualTo(0)
+ assertThat(repo.getVisibleTaskCount(displayId = 9)).isEqualTo(0)
// Hiding a not existing task, count remains at 0
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 999, visible = false)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
}
@Test
- fun visibleTaskCount_multipleDisplays_returnsCorrectCount() {
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+ fun getVisibleTaskCount_multipleDisplays_returnsCorrectCount() {
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
// New task on default display increments count for that display only
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
- assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
// New task on secondary display, increments count for that display only
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 2, visible = true)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
- assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
// Marking task visible on another display, updates counts for both displays
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
// Marking task that is on secondary display, hidden on default display, does not affect
// secondary display
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
// Hiding a task on that display, decrements count
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
}
@Test
@@ -428,7 +471,9 @@
fun removeFreeformTask_removesTaskBoundsBeforeMaximize() {
val taskId = 1
repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200))
+
repo.removeFreeformTask(THIRD_DISPLAY, taskId)
+
assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull()
}
@@ -436,7 +481,9 @@
fun saveBoundsBeforeMaximize_boundsSavedByTaskId() {
val taskId = 1
val bounds = Rect(0, 0, 200, 200)
+
repo.saveBoundsBeforeMaximize(taskId, bounds)
+
assertThat(repo.removeBoundsBeforeMaximize(taskId)).isEqualTo(bounds)
}
@@ -446,17 +493,20 @@
val bounds = Rect(0, 0, 200, 200)
repo.saveBoundsBeforeMaximize(taskId, bounds)
repo.removeBoundsBeforeMaximize(taskId)
- assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull()
+
+ val boundsBeforeMaximize = repo.removeBoundsBeforeMaximize(taskId)
+
+ assertThat(boundsBeforeMaximize).isNull()
}
@Test
- fun minimizeTaskNotCalled_noTasksMinimized() {
+ fun isMinimizedTask_minimizeTaskNotCalled_noTasksMinimized() {
assertThat(repo.isMinimizedTask(taskId = 0)).isFalse()
assertThat(repo.isMinimizedTask(taskId = 1)).isFalse()
}
@Test
- fun minimizeTask_onlyThatTaskIsMinimized() {
+ fun minimizeTask_minimizesCorrectTask() {
repo.minimizeTask(displayId = 0, taskId = 0)
assertThat(repo.isMinimizedTask(taskId = 0)).isTrue()
@@ -465,8 +515,9 @@
}
@Test
- fun unminimizeTask_taskNoLongerMinimized() {
+ fun unminimizeTask_unminimizesTask() {
repo.minimizeTask(displayId = 0, taskId = 0)
+
repo.unminimizeTask(displayId = 0, taskId = 0)
assertThat(repo.isMinimizedTask(taskId = 0)).isFalse()
@@ -478,6 +529,7 @@
fun unminimizeTask_nonExistentTask_doesntCrash() {
repo.unminimizeTask(displayId = 0, taskId = 0)
+ // No change
assertThat(repo.isMinimizedTask(taskId = 0)).isFalse()
assertThat(repo.isMinimizedTask(taskId = 1)).isFalse()
assertThat(repo.isMinimizedTask(taskId = 2)).isFalse()
@@ -485,41 +537,44 @@
@Test
- fun updateVisibleFreeformTasks_toVisible_taskIsUnminimized() {
+ fun updateVisibleFreeformTasks_minimizedTaskBecomesVisible_unminimizesTask() {
repo.minimizeTask(displayId = 10, taskId = 2)
-
repo.updateVisibleFreeformTasks(displayId = 10, taskId = 2, visible = true)
- assertThat(repo.isMinimizedTask(taskId = 2)).isFalse()
+ val isMinimizedTask = repo.isMinimizedTask(taskId = 2)
+
+ assertThat(isMinimizedTask).isFalse()
}
@Test
- fun getActiveNonMinimizedTasksOrderedFrontToBack_returnsFreeformTasksInCorrectOrder() {
+ fun getActiveNonMinimizedOrderedTasks_returnsFreeformTasksInCorrectOrder() {
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 3)
- // The front-most task will be the one added last through addOrMoveFreeformTaskToTop
+ // The front-most task will be the one added last through `addOrMoveFreeformTaskToTop`
repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 3)
repo.addOrMoveFreeformTaskToTop(displayId = 0, taskId = 2)
repo.addOrMoveFreeformTaskToTop(displayId = 0, taskId = 1)
- assertThat(repo.getActiveNonMinimizedTasksOrderedFrontToBack(displayId = 0))
- .containsExactly(1, 2, 3).inOrder()
+ val tasks = repo.getActiveNonMinimizedOrderedTasks(displayId = 0)
+
+ assertThat(tasks).containsExactly(1, 2, 3).inOrder()
}
@Test
- fun getActiveNonMinimizedTasksOrderedFrontToBack_minimizedTaskNotIncluded() {
+ fun getActiveNonMinimizedOrderedTasks_excludesMinimizedTasks() {
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 3)
- // The front-most task will be the one added last through addOrMoveFreeformTaskToTop
+ // The front-most task will be the one added last through `addOrMoveFreeformTaskToTop`
repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 3)
repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 2)
repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 1)
repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
- assertThat(repo.getActiveNonMinimizedTasksOrderedFrontToBack(
- displayId = DEFAULT_DISPLAY)).containsExactly(1, 3).inOrder()
+ val tasks = repo.getActiveNonMinimizedOrderedTasks(displayId = DEFAULT_DISPLAY)
+
+ assertThat(tasks).containsExactly(1, 3).inOrder()
}
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/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 409b877..81e6d07 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -442,6 +442,27 @@
}
@Test
+ public void testTransitionFilterAnimOverride() {
+ TransitionFilter filter = new TransitionFilter();
+ filter.mRequirements =
+ new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
+ filter.mRequirements[0].mCustomAnimation = true;
+ filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+
+ final RunningTaskInfo taskInf = createTaskInfo(1);
+ final TransitionInfo openTask = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, taskInf).build();
+ assertFalse(filter.matches(openTask));
+
+ final TransitionInfo.AnimationOptions overOpts =
+ TransitionInfo.AnimationOptions.makeCustomAnimOptions("pakname", 0, 0, 0, true);
+ final TransitionInfo openTaskOpts = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, taskInf).build();
+ openTaskOpts.getChanges().get(0).setAnimationOptions(overOpts);
+ assertTrue(filter.matches(openTaskOpts));
+ }
+
+ @Test
public void testRegisteredRemoteTransition() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 4b069f9..cee9307 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -20,6 +20,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -411,6 +413,80 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+ public void updateRelayoutParams_defaultHeader_addsForceConsumingFlag() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(0);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) != 0).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+ public void updateRelayoutParams_customHeader_noForceConsumptionFlag() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(
+ APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) == 0).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS)
+ public void updateRelayoutParams_header_addsForceConsumingCaptionBar() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(
+ (relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0)
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS)
+ public void updateRelayoutParams_handle_skipsForceConsumingCaptionBar() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(
+ (relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) == 0)
+ .isTrue();
+ }
+
@DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
public void relayout_fullscreenTask_appliesTransactionImmediately() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
@@ -708,7 +784,7 @@
decoration.setOnLeftSnapClickListener(l);
decoration.setOnRightSnapClickListener(l);
decoration.createMaximizeMenu();
- verify(menu).show(any(), any(), any(), mOnMaxMenuHoverChangeListener.capture());
+ verify(menu).show(any(), any(), any(), mOnMaxMenuHoverChangeListener.capture(), any());
}
private void fillRoundedCornersResources(int fillValue) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 2d1bf14..ca6e03c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
@@ -56,7 +57,6 @@
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
@@ -75,7 +75,6 @@
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
@@ -781,8 +780,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
- public void testRelayout_captionInsetForceConsume() {
+ public void testRelayout_captionInsetSourceFlags() {
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController)
.getDisplay(Display.DEFAULT_DISPLAY);
@@ -794,11 +792,14 @@
final ActivityManager.RunningTaskInfo taskInfo =
builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build();
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+ mRelayoutParams.mInsetSourceFlags =
+ FLAG_FORCE_CONSUMING | FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
windowDecor.relayout(taskInfo);
- // Caption inset source should be force-consuming.
+ // Caption inset source should add params' flags.
verify(mMockWindowContainerTransaction).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any(), eq(FLAG_FORCE_CONSUMING));
+ eq(0) /* index */, eq(captionBar()), any(), any(),
+ eq(FLAG_FORCE_CONSUMING | FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
index 3b49055..28b4eb6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
@@ -29,7 +29,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.kotlin.any
+import org.mockito.kotlin.argThat
import org.mockito.kotlin.eq
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -66,6 +66,8 @@
@Test
fun testReleaseView_ViewRemoved() {
+ val flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
viewContainer = AdditionalSystemViewContainer(
mockContext,
TASK_ID,
@@ -73,9 +75,15 @@
Y,
WIDTH,
HEIGHT,
+ flags,
R.layout.desktop_mode_window_decor_handle_menu
)
- verify(mockWindowManager).addView(eq(mockView), any())
+ verify(mockWindowManager).addView(
+ eq(mockView),
+ argThat {
+ lp -> (lp as WindowManager.LayoutParams).flags == flags
+ }
+ )
viewContainer.releaseView()
verify(mockWindowManager).removeViewImmediate(mockView)
}
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index d415700..010c4e8 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -33,9 +33,6 @@
#define DEBUG_PARCEL 0
static jclass gBitmap_class;
-static jfieldID gBitmap_nativePtr;
-static jmethodID gBitmap_constructorMethodID;
-static jmethodID gBitmap_reinitMethodID;
namespace android {
@@ -183,6 +180,9 @@
void reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info,
bool isPremultiplied)
{
+ static jmethodID gBitmap_reinitMethodID =
+ GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
+
// The caller needs to have already set the alpha type properly, so the
// native SkBitmap stays in sync with the Java Bitmap.
assert_premultiplied(info, isPremultiplied);
@@ -194,6 +194,10 @@
jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
int density) {
+ static jmethodID gBitmap_constructorMethodID =
+ GetMethodIDOrDie(env, gBitmap_class,
+ "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
+
bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
// The caller needs to have already set the alpha type properly, so the
@@ -232,11 +236,17 @@
using namespace android;
using namespace android::bitmap;
+static inline jlong getNativePtr(JNIEnv* env, jobject bitmap) {
+ static jfieldID gBitmap_nativePtr =
+ GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J");
+ return env->GetLongField(bitmap, gBitmap_nativePtr);
+}
+
Bitmap* GraphicsJNI::getNativeBitmap(JNIEnv* env, jobject bitmap) {
SkASSERT(env);
SkASSERT(bitmap);
SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
- jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
+ jlong bitmapHandle = getNativePtr(env, bitmap);
LocalScopedBitmap localBitmap(bitmapHandle);
return localBitmap.valid() ? &localBitmap->bitmap() : nullptr;
}
@@ -246,7 +256,7 @@
SkASSERT(env);
SkASSERT(bitmap);
SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
- jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
+ jlong bitmapHandle = getNativePtr(env, bitmap);
LocalScopedBitmap localBitmap(bitmapHandle);
if (outRowBytes) {
*outRowBytes = localBitmap->rowBytes();
@@ -1269,9 +1279,6 @@
int register_android_graphics_Bitmap(JNIEnv* env)
{
gBitmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Bitmap"));
- gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J");
- gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
- gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
uirenderer::HardwareBufferHelpers::init();
return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods,
NELEM(gBitmapMethods));
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 8acaf3b..e575dae 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5263,6 +5263,8 @@
* main thread.)
*/
public void setCallback(@Nullable /* MediaCodec. */ Callback cb, @Nullable Handler handler) {
+ boolean setCallbackStallFlag =
+ GetFlag(() -> android.media.codec.Flags.setCallbackStall());
if (cb != null) {
synchronized (mListenerLock) {
EventHandler newHandler = getEventHandlerOn(handler, mCallbackHandler);
@@ -5270,7 +5272,7 @@
// even if we were to extend this to be callable dynamically, it must
// be called when codec is flushed, so no messages are pending.
if (newHandler != mCallbackHandler) {
- if (android.media.codec.Flags.setCallbackStall()) {
+ if (setCallbackStallFlag) {
logAndRun(
"[new handler] removeMessages(SET_CALLBACK)",
() -> {
@@ -5289,7 +5291,7 @@
}
}
} else if (mCallbackHandler != null) {
- if (android.media.codec.Flags.setCallbackStall()) {
+ if (setCallbackStallFlag) {
logAndRun(
"[null handler] removeMessages(SET_CALLBACK)",
() -> {
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 0667bfd..1930c3d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -107,7 +107,8 @@
* #SCANNING_STATE_WHILE_INTERACTIVE}.
*
* <p>Routers requesting unrestricted scanning must hold {@link
- * Manifest.permission#MEDIA_ROUTING_CONTROL}.
+ * Manifest.permission#MEDIA_ROUTING_CONTROL} or {@link
+ * Manifest.permission#MEDIA_CONTENT_CONTROL}.
*
* @hide
*/
@@ -522,11 +523,16 @@
*
* <p>{@code scanRequest} specifies relevant scanning options, like whether the system should
* scan with the screen off. Screen off scanning requires {@link
- * Manifest.permission#MEDIA_ROUTING_CONTROL}
+ * Manifest.permission#MEDIA_ROUTING_CONTROL} or {@link
+ * Manifest.permission#MEDIA_CONTENT_CONTROL}.
*
* <p>Proxy routers use the registered {@link RouteDiscoveryPreference} of their target routers.
*
* @return A unique {@link ScanToken} that identifies the scan request.
+ * @throws SecurityException If a {@link ScanRequest} with {@link
+ * ScanRequest.Builder#setScreenOffScan} true is passed, while not holding {@link
+ * Manifest.permission#MEDIA_ROUTING_CONTROL} or {@link
+ * Manifest.permission#MEDIA_CONTENT_CONTROL}.
*/
@FlaggedApi(FLAG_ENABLE_SCREEN_OFF_SCANNING)
@NonNull
@@ -1745,8 +1751,9 @@
/**
* Sets whether the app is requesting to scan even while the screen is off, bypassing
- * default scanning restrictions. Only companion apps holding {@link
- * Manifest.permission#MEDIA_ROUTING_CONTROL} should set this to {@code true}.
+ * default scanning restrictions. Only apps holding {@link
+ * Manifest.permission#MEDIA_ROUTING_CONTROL} or {@link
+ * Manifest.permission#MEDIA_CONTENT_CONTROL} should set this to {@code true}.
*
* @see #requestScan(ScanRequest)
*/
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 91c4f11..7c41f96 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -134,3 +134,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_full_scan_with_media_content_control"
+ namespace: "media_better_together"
+ description: "Allows holders of the MEDIA_CONTENT_CONTROL permission to scan for routes while not in the foreground."
+ bug: "352401364"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 1d6e38d..8a877b8 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -165,7 +165,7 @@
/**
* Creates a new session. The session will automatically be registered with
- * the system but will not be published until {@link #setActive(boolean)
+ * the system, but will not be published until {@link #setActive(boolean)
* setActive(true)} is called. You must call {@link #release()} when
* finished with the session.
* <p>
diff --git a/mms/java/android/telephony/MmsManager.java b/mms/java/android/telephony/MmsManager.java
index b893b45..ac29277 100644
--- a/mms/java/android/telephony/MmsManager.java
+++ b/mms/java/android/telephony/MmsManager.java
@@ -26,6 +26,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import com.android.internal.telephony.IMms;
@@ -69,9 +70,9 @@
return;
}
- iMms.sendMessage(subId, ActivityThread.currentPackageName(), contentUri,
- locationUrl, configOverrides, sentIntent, messageId,
- mContext.getAttributionTag());
+ iMms.sendMessage(subId, /* placeholder callingUser= */ UserHandle.USER_NULL,
+ ActivityThread.currentPackageName(), contentUri, locationUrl,
+ configOverrides, sentIntent, messageId, mContext.getAttributionTag());
} catch (RemoteException e) {
// Ignore it
}
@@ -101,9 +102,9 @@
if (iMms == null) {
return;
}
- iMms.downloadMessage(subId, ActivityThread.currentPackageName(),
- locationUrl, contentUri, configOverrides, downloadedIntent,
- messageId, mContext.getAttributionTag());
+ iMms.downloadMessage(subId, /* placeholder callingUser= */ UserHandle.USER_NULL,
+ ActivityThread.currentPackageName(), locationUrl, contentUri,
+ configOverrides, downloadedIntent, messageId, mContext.getAttributionTag());
} catch (RemoteException e) {
// Ignore it
}
diff --git a/mms/java/com/android/internal/telephony/IMms.aidl b/mms/java/com/android/internal/telephony/IMms.aidl
index 3cdde10..1c75951 100644
--- a/mms/java/com/android/internal/telephony/IMms.aidl
+++ b/mms/java/com/android/internal/telephony/IMms.aidl
@@ -29,6 +29,7 @@
* Send an MMS message with attribution tag.
*
* @param subId the SIM id
+ * @param callingUser user id of the calling app
* @param callingPkg the package name of the calling app
* @param contentUri the content uri from which to read MMS message encoded in standard MMS
* PDU format
@@ -40,7 +41,7 @@
* @param messageId An id that uniquely identifies the message requested to be sent.
* @param attributionTag a tag that attributes the call to a client App.
*/
- void sendMessage(int subId, String callingPkg, in Uri contentUri,
+ void sendMessage(int subId, in int callingUser, String callingPkg, in Uri contentUri,
String locationUrl, in Bundle configOverrides, in PendingIntent sentIntent,
in long messageId, String attributionTag);
@@ -48,6 +49,7 @@
* Download an MMS message using known location and transaction id
*
* @param subId the SIM id
+ * @param callingUser user id of the calling app
* @param callingPkg the package name of the calling app
* @param locationUrl the location URL of the MMS message to be downloaded, usually obtained
* from the MMS WAP push notification
@@ -60,7 +62,7 @@
* @param messageId An id that uniquely identifies the message requested to be downloaded.
* @param attributionTag a tag that attributes the call to a client App.
*/
- void downloadMessage(int subId, String callingPkg, String locationUrl,
+ void downloadMessage(int subId, in int callingUser, String callingPkg, String locationUrl,
in Uri contentUri, in Bundle configOverrides,
in PendingIntent downloadedIntent, in long messageId, String attributionTag);
@@ -82,6 +84,7 @@
/**
* Import a multimedia message into system's MMS store
*
+ * @param callingUser user id of the calling app
* @param callingPkg the package name of the calling app
* @param contentUri the content uri from which to read PDU of the message to import
* @param messageId the optional message id
@@ -90,7 +93,7 @@
* @param read if the message is read
* @return the message URI, null if failed
*/
- Uri importMultimediaMessage(String callingPkg, in Uri contentUri, String messageId,
+ Uri importMultimediaMessage(in int callingUser, String callingPkg, in Uri contentUri, String messageId,
long timestampSecs, boolean seen, boolean read);
/**
@@ -146,11 +149,12 @@
/**
* Add a multimedia message draft to system MMS store
*
+ * @param callingUser user id of the calling app
* @param callingPkg the package name of the calling app
* @param contentUri the content Uri from which to read PDU data of the draft MMS
* @return the URI of the stored draft message
*/
- Uri addMultimediaMessageDraft(String callingPkg, in Uri contentUri);
+ Uri addMultimediaMessageDraft(in int callingUser, String callingPkg, in Uri contentUri);
/**
* Send a system stored MMS message
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 403e219..83a986b1 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -112,3 +112,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "settings_catalyst"
+ namespace: "android_settings"
+ description: "Settings catalyst project migration"
+ bug: "323791114"
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 53441c0..0543177 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -11,6 +11,7 @@
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -475,14 +476,26 @@
}
/**
+ * Gets string metadata from Fast Pair customized fields.
+ *
+ * @param bluetoothDevice the BluetoothDevice to get metadata
+ * @return the string metadata
+ */
+ @Nullable
+ public static String getFastPairCustomizedField(
+ @Nullable BluetoothDevice bluetoothDevice, @NonNull String key) {
+ String data = getStringMetaData(bluetoothDevice, METADATA_FAST_PAIR_CUSTOMIZED_FIELDS);
+ return extraTagValue(key, data);
+ }
+
+ /**
* Get URI Bluetooth metadata for extra control
*
* @param bluetoothDevice the BluetoothDevice to get metadata
* @return the URI metadata
*/
public static String getControlUriMetaData(BluetoothDevice bluetoothDevice) {
- String data = getStringMetaData(bluetoothDevice, METADATA_FAST_PAIR_CUSTOMIZED_FIELDS);
- return extraTagValue(KEY_HEARABLE_CONTROL_SLICE, data);
+ return getFastPairCustomizedField(bluetoothDevice, KEY_HEARABLE_CONTROL_SLICE);
}
/**
@@ -835,9 +848,9 @@
/** Get primary device group id in broadcast. */
@WorkerThread
- public static int getPrimaryGroupIdForBroadcast(@NonNull Context context) {
+ public static int getPrimaryGroupIdForBroadcast(@NonNull ContentResolver contentResolver) {
return Settings.Secure.getInt(
- context.getContentResolver(),
+ contentResolver,
getPrimaryGroupIdUriForBroadcast(),
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
}
@@ -846,9 +859,13 @@
@Nullable
@WorkerThread
public static CachedBluetoothDevice getSecondaryDeviceForBroadcast(
- @NonNull Context context, @Nullable LocalBluetoothManager localBtManager) {
+ @NonNull ContentResolver contentResolver,
+ @Nullable LocalBluetoothManager localBtManager) {
if (localBtManager == null) return null;
- int primaryGroupId = getPrimaryGroupIdForBroadcast(context);
+ LocalBluetoothLeBroadcast broadcast =
+ localBtManager.getProfileManager().getLeAudioBroadcastProfile();
+ if (!broadcast.isEnabled(null)) return null;
+ int primaryGroupId = getPrimaryGroupIdForBroadcast(contentResolver);
if (primaryGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) return null;
LocalBluetoothLeBroadcastAssistant assistant =
localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
@@ -866,4 +883,4 @@
}
return null;
}
-}
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt
new file mode 100644
index 0000000..0bcf7fe
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth
+
+import android.bluetooth.BluetoothLeBroadcast
+import android.bluetooth.BluetoothLeBroadcastMetadata
+import com.android.internal.util.ConcurrentUtils
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+/** [Flow] for [BluetoothLeBroadcast.Callback] source start/stop events */
+val LocalBluetoothLeBroadcast.onBroadcastStartedOrStopped: Flow<Unit>
+ get() =
+ callbackFlow {
+ val listener =
+ object : BluetoothLeBroadcast.Callback {
+ override fun onBroadcastStarted(reason: Int, broadcastId: Int) {
+ launch { trySend(Unit) }
+ }
+
+ override fun onBroadcastStartFailed(reason: Int) {
+ launch { trySend(Unit) }
+ }
+
+ override fun onBroadcastStopped(reason: Int, broadcastId: Int) {
+ launch { trySend(Unit) }
+ }
+
+ override fun onBroadcastStopFailed(reason: Int) {
+ launch { trySend(Unit) }
+ }
+
+ override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
+
+ override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
+
+ override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
+
+ override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {}
+
+ override fun onBroadcastMetadataChanged(
+ broadcastId: Int,
+ metadata: BluetoothLeBroadcastMetadata
+ ) {}
+ }
+ registerServiceCallBack(
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ listener,
+ )
+ awaitClose { unregisterServiceCallBack(listener) }
+ }
+ .buffer(capacity = Channel.CONFLATED)
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java
index 91c1a59..eefceae 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java
@@ -21,6 +21,9 @@
import android.os.Parcelable;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
/** A data class representing the state of an action/switch preference. */
public class ActionSwitchPreferenceState extends DeviceSettingPreferenceState
@@ -133,4 +136,15 @@
public Bundle getExtras() {
return mExtras;
}
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof ActionSwitchPreferenceState other)) return false;
+ return mChecked == other.mChecked;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mChecked);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java
index 52e520e..bfd1d1a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java
@@ -21,6 +21,7 @@
import android.os.Parcelable;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import java.util.Objects;
@@ -134,4 +135,15 @@
public Bundle getExtras() {
return mExtras;
}
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof DeviceInfo other)) return false;
+ return Objects.equals(mBluetoothAddress, other.getBluetoothAddress());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mBluetoothAddress);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java
index 63fd4eb..b505f27 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java
@@ -21,6 +21,7 @@
import android.os.Parcelable;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import java.util.Objects;
@@ -162,4 +163,16 @@
public Bundle getExtras() {
return mExtras;
}
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof DeviceSettingState other)) return false;
+ return mSettingId == other.mSettingId
+ && Objects.equals(mPreferenceState, other.mPreferenceState);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSettingId, mPreferenceState);
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
copy to packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
index 37c9552..647611e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.settingslib.bluetooth.devicesettings;
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
-import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
+import com.android.settingslib.bluetooth.devicesettings.DeviceInfo;
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig;
-val Kosmos.partitionedGridLayout by
- Kosmos.Fixture { PartitionedGridLayout(partitionedGridViewModel) }
+interface IDeviceSettingsConfigProviderService {
+ DeviceSettingsConfig getDeviceSettingsConfig(in DeviceInfo device);
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java
index 239df0b..d9ce4c5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java
@@ -21,6 +21,9 @@
import android.os.Parcelable;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
/** A data class representing a multi-toggle preference state. */
public class MultiTogglePreferenceState extends DeviceSettingPreferenceState implements Parcelable {
@@ -123,4 +126,15 @@
public Bundle getExtras() {
return mExtras;
}
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof MultiTogglePreferenceState other)) return false;
+ return mState == other.mState;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mState);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
new file mode 100644
index 0000000..9ff5c43
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth.devicesettings.data.repository
+
+import android.bluetooth.BluetoothAdapter
+import android.content.Context
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.DeviceSetting
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingPreferenceState
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+
+/** Provides functionality to control bluetooth device settings. */
+interface DeviceSettingRepository {
+ /** Gets config for the bluetooth device, returns null if failed. */
+ suspend fun getDeviceSettingsConfig(cachedDevice: CachedBluetoothDevice): DeviceSettingsConfig?
+
+ /** Gets all device settings for the bluetooth device. */
+ fun getDeviceSettingList(
+ cachedDevice: CachedBluetoothDevice,
+ ): Flow<List<DeviceSetting>?>
+
+ /** Gets device setting for the bluetooth device. */
+ fun getDeviceSetting(
+ cachedDevice: CachedBluetoothDevice,
+ @DeviceSettingId settingId: Int
+ ): Flow<DeviceSetting?>
+
+ /** Updates device setting for the bluetooth device. */
+ suspend fun updateDeviceSettingState(
+ cachedDevice: CachedBluetoothDevice,
+ @DeviceSettingId deviceSettingId: Int,
+ deviceSettingPreferenceState: DeviceSettingPreferenceState,
+ )
+}
+
+class DeviceSettingRepositoryImpl(
+ private val context: Context,
+ private val bluetoothAdaptor: BluetoothAdapter,
+ private val coroutineScope: CoroutineScope,
+ private val backgroundCoroutineContext: CoroutineContext,
+) : DeviceSettingRepository {
+ private val deviceSettings =
+ ConcurrentHashMap<CachedBluetoothDevice, DeviceSettingServiceConnection>()
+
+ override suspend fun getDeviceSettingsConfig(
+ cachedDevice: CachedBluetoothDevice
+ ): DeviceSettingsConfig? = createConnectionIfAbsent(cachedDevice).getDeviceSettingsConfig()
+
+ override fun getDeviceSettingList(
+ cachedDevice: CachedBluetoothDevice
+ ): Flow<List<DeviceSetting>?> = createConnectionIfAbsent(cachedDevice).getDeviceSettingList()
+
+ override fun getDeviceSetting(
+ cachedDevice: CachedBluetoothDevice,
+ settingId: Int
+ ): Flow<DeviceSetting?> = createConnectionIfAbsent(cachedDevice).getDeviceSetting(settingId)
+
+ override suspend fun updateDeviceSettingState(
+ cachedDevice: CachedBluetoothDevice,
+ @DeviceSettingId deviceSettingId: Int,
+ deviceSettingPreferenceState: DeviceSettingPreferenceState,
+ ) =
+ createConnectionIfAbsent(cachedDevice)
+ .updateDeviceSettings(deviceSettingId, deviceSettingPreferenceState)
+
+ private fun createConnectionIfAbsent(
+ cachedDevice: CachedBluetoothDevice
+ ): DeviceSettingServiceConnection =
+ deviceSettings.computeIfAbsent(cachedDevice) {
+ DeviceSettingServiceConnection(
+ cachedDevice,
+ context,
+ bluetoothAdaptor,
+ coroutineScope,
+ backgroundCoroutineContext,
+ )
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
new file mode 100644
index 0000000..d6b2862
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth.devicesettings.data.repository
+
+import android.bluetooth.BluetoothAdapter
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.DeviceInfo
+import com.android.settingslib.bluetooth.devicesettings.DeviceSetting
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingPreferenceState
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingState
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
+import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsConfigProviderService
+import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsListener
+import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsProviderService
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emitAll
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class DeviceSettingServiceConnection(
+ private val cachedDevice: CachedBluetoothDevice,
+ private val context: Context,
+ private val bluetoothAdaptor: BluetoothAdapter,
+ private val coroutineScope: CoroutineScope,
+ private val backgroundCoroutineContext: CoroutineContext,
+) {
+ data class EndPoint(
+ private val packageName: String,
+ private val className: String?,
+ private val intentAction: String,
+ ) {
+ fun toIntent(): Intent =
+ Intent().apply {
+ if (className.isNullOrBlank()) {
+ setPackage(packageName)
+ } else {
+ setClassName(packageName, className)
+ }
+ setAction(intentAction)
+ }
+ }
+
+ private var config = AtomicReference<DeviceSettingsConfig?>(null)
+ private var idToSetting = AtomicReference<Flow<Map<Int, DeviceSetting>>?>(null)
+
+ /** Gets [DeviceSettingsConfig] for the device, return null when failed. */
+ suspend fun getDeviceSettingsConfig(): DeviceSettingsConfig? =
+ config.computeIfAbsent {
+ getConfigServiceBindingIntent(cachedDevice)
+ .flatMapLatest { getService(it) }
+ .map { it?.let { IDeviceSettingsConfigProviderService.Stub.asInterface(it) } }
+ .map {
+ it?.getDeviceSettingsConfig(
+ deviceInfo { setBluetoothAddress(cachedDevice.address) }
+ )
+ }
+ .first()
+ }
+
+ /** Gets all device settings for the device. */
+ fun getDeviceSettingList(): Flow<List<DeviceSetting>> =
+ getSettingIdToItemMapping().map { it.values.toList() }
+
+ /** Gets the device settings with the ID for the device. */
+ fun getDeviceSetting(@DeviceSettingId deviceSettingId: Int): Flow<DeviceSetting?> =
+ getSettingIdToItemMapping().map { it[deviceSettingId] }
+
+ /** Updates the device setting state for the device. */
+ suspend fun updateDeviceSettings(
+ @DeviceSettingId deviceSettingId: Int,
+ deviceSettingPreferenceState: DeviceSettingPreferenceState,
+ ) {
+ getDeviceSettingsConfig()?.let { config ->
+ (config.mainContentItems + config.moreSettingsItems)
+ .find { it.settingId == deviceSettingId }
+ ?.let {
+ getSettingsProviderServices()
+ ?.get(EndPoint(it.packageName, it.className, it.intentAction))
+ ?.filterNotNull()
+ ?.first()
+ }
+ ?.updateDeviceSettings(
+ deviceInfo { setBluetoothAddress(cachedDevice.address) },
+ DeviceSettingState.Builder()
+ .setSettingId(deviceSettingId)
+ .setPreferenceState(deviceSettingPreferenceState)
+ .build()
+ )
+ }
+ }
+
+ private suspend fun getSettingsProviderServices():
+ Map<EndPoint, StateFlow<IDeviceSettingsProviderService?>>? =
+ getDeviceSettingsConfig()
+ ?.let { config ->
+ (config.mainContentItems + config.moreSettingsItems).map {
+ EndPoint(
+ packageName = it.packageName,
+ className = it.className,
+ intentAction = it.intentAction
+ )
+ }
+ }
+ ?.distinct()
+ ?.associateBy(
+ { it },
+ { endpoint ->
+ services.computeIfAbsent(endpoint) {
+ getService(endpoint.toIntent())
+ .map { service ->
+ IDeviceSettingsProviderService.Stub.asInterface(service)
+ }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+ }
+ }
+ )
+
+ private fun getSettingIdToItemMapping(): Flow<Map<Int, DeviceSetting>> =
+ idToSetting.computeIfAbsent {
+ flow {
+ getSettingsProviderServices()
+ ?.values
+ ?.map {
+ it.flatMapLatest { service ->
+ if (service != null) {
+ getDeviceSettingsFromService(cachedDevice, service)
+ } else {
+ flowOf(emptyList())
+ }
+ }
+ }
+ ?.let { items -> combine(items) { it.toList().flatten() } }
+ ?.map { items -> items.associateBy { it.settingId } }
+ ?.let { emitAll(it) }
+ }
+ .shareIn(
+ scope = coroutineScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1
+ )
+ }!!
+
+ private fun getDeviceSettingsFromService(
+ cachedDevice: CachedBluetoothDevice,
+ service: IDeviceSettingsProviderService
+ ): Flow<List<DeviceSetting>> {
+ return callbackFlow {
+ val listener =
+ object : IDeviceSettingsListener.Stub() {
+ override fun onDeviceSettingsChanged(settings: List<DeviceSetting>) {
+ launch { send(settings) }
+ }
+ }
+ val deviceInfo = deviceInfo { setBluetoothAddress(cachedDevice.address) }
+ service.registerDeviceSettingsListener(deviceInfo, listener)
+ awaitClose { service.unregisterDeviceSettingsListener(deviceInfo, listener) }
+ }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
+ }
+
+ private fun getService(intent: Intent): Flow<IBinder?> {
+ return callbackFlow {
+ val serviceConnection =
+ object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName, service: IBinder) {
+ launch { send(service) }
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ launch { send(null) }
+ }
+ }
+ if (!context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)) {
+ launch { send(null) }
+ }
+ awaitClose { context.unbindService(serviceConnection) }
+ }
+ }
+
+ private fun getConfigServiceBindingIntent(cachedDevice: CachedBluetoothDevice): Flow<Intent> {
+ return callbackFlow {
+ val listener =
+ BluetoothAdapter.OnMetadataChangedListener { device, key, _ ->
+ if (
+ key == METADATA_FAST_PAIR_CUSTOMIZED_FIELDS &&
+ cachedDevice.device == device
+ ) {
+ launch { tryGetEndpointFromMetadata(cachedDevice)?.let { send(it) } }
+ }
+ }
+ bluetoothAdaptor.addOnMetadataChangedListener(
+ cachedDevice.device,
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ listener,
+ )
+ awaitClose {
+ bluetoothAdaptor.removeOnMetadataChangedListener(cachedDevice.device, listener)
+ }
+ }
+ .onStart { tryGetEndpointFromMetadata(cachedDevice)?.let { emit(it) } }
+ .distinctUntilChanged()
+ .map { it.toIntent() }
+ .flowOn(backgroundCoroutineContext)
+ }
+
+ private suspend fun tryGetEndpointFromMetadata(cachedDevice: CachedBluetoothDevice): EndPoint? =
+ withContext(backgroundCoroutineContext) {
+ val packageName =
+ BluetoothUtils.getFastPairCustomizedField(
+ cachedDevice.device,
+ CONFIG_SERVICE_PACKAGE_NAME,
+ ) ?: return@withContext null
+ val className =
+ BluetoothUtils.getFastPairCustomizedField(
+ cachedDevice.device,
+ CONFIG_SERVICE_CLASS_NAME
+ ) ?: return@withContext null
+ val intentAction =
+ BluetoothUtils.getFastPairCustomizedField(
+ cachedDevice.device,
+ CONFIG_SERVICE_INTENT_ACTION
+ ) ?: return@withContext null
+ EndPoint(packageName, className, intentAction)
+ }
+
+ private inline fun <T> AtomicReference<T?>.computeIfAbsent(producer: () -> T): T? =
+ get() ?: producer().let { compareAndExchange(null, it) ?: it }
+
+ private inline fun deviceInfo(block: DeviceInfo.Builder.() -> Unit): DeviceInfo {
+ return DeviceInfo.Builder().apply { block() }.build()
+ }
+
+ companion object {
+ const val METADATA_FAST_PAIR_CUSTOMIZED_FIELDS: Int = 25
+ const val CONFIG_SERVICE_PACKAGE_NAME = "DEVICE_SETTINGS_CONFIG_PACKAGE_NAME"
+ const val CONFIG_SERVICE_CLASS_NAME = "DEVICE_SETTINGS_CONFIG_CLASS"
+ const val CONFIG_SERVICE_INTENT_ACTION = "DEVICE_SETTINGS_CONFIG_ACTION"
+
+ val services = ConcurrentHashMap<EndPoint, StateFlow<IDeviceSettingsProviderService?>>()
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 49b974f..87ab6b3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -73,15 +73,22 @@
}
fun activateMode(id: String) {
- val oldMode = mutableModesFlow.value.find { it.id == id } ?: return
- removeMode(id)
- mutableModesFlow.value += TestModeBuilder(oldMode).setActive(true).build()
+ updateModeActiveState(id = id, isActive = true)
}
fun deactivateMode(id: String) {
- val oldMode = mutableModesFlow.value.find { it.id == id } ?: return
- removeMode(id)
- mutableModesFlow.value += TestModeBuilder(oldMode).setActive(false).build()
+ updateModeActiveState(id = id, isActive = false)
+ }
+
+ // Update the active state while maintaining the mode's position in the list
+ private fun updateModeActiveState(id: String, isActive: Boolean) {
+ val modes = mutableModesFlow.value.toMutableList()
+ val index = modes.indexOfFirst { it.id == id }
+ if (index < 0) {
+ throw IllegalArgumentException("mode $id not found")
+ }
+ modes[index] = TestModeBuilder(modes[index]).setActive(isActive).build()
+ mutableModesFlow.value = modes
}
}
@@ -101,7 +108,8 @@
suppressedVisualEffects,
state,
priorityConversationSenders,
- ))
+ )
+ )
private fun newMode(id: String, active: Boolean = false): ZenMode {
return TestModeBuilder().setId(id).setName("Mode $id").setActive(active).build()
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java
index 054c849..c48694c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java
@@ -159,9 +159,9 @@
});
if (mCancelIsNeutral) {
- builder.setNeutralButton(R.string.cancel, null);
+ builder.setNeutralButton(android.R.string.cancel, null);
} else {
- builder.setNegativeButton(R.string.cancel, null);
+ builder.setNegativeButton(android.R.string.cancel, null);
}
View contentView = getContentView();
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index 2f7cdd6..a06f084 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -16,6 +16,9 @@
package com.android.settingslib.notification.modes;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
+
import android.app.AutomaticZenRule;
import android.app.NotificationManager;
import android.content.ComponentName;
@@ -144,8 +147,15 @@
}
public TestModeBuilder setEnabled(boolean enabled) {
+ return setEnabled(enabled, /* byUser= */ false);
+ }
+
+ public TestModeBuilder setEnabled(boolean enabled, boolean byUser) {
mRule.setEnabled(enabled);
mConfigZenRule.enabled = enabled;
+ if (!enabled) {
+ mConfigZenRule.disabledOrigin = byUser ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_UNKNOWN;
+ }
return this;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
index 960df63..271d5c4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
@@ -35,7 +35,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.content.res.AppCompatResources;
import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.Futures;
@@ -104,7 +103,7 @@
return context.getDrawable(iconResId);
} else {
Context appContext = context.createPackageContext(pkg, 0);
- Drawable appDrawable = AppCompatResources.getDrawable(appContext, iconResId);
+ Drawable appDrawable = appContext.getDrawable(iconResId);
return getMonochromeIconIfPresent(appDrawable);
}
})).catching(Exception.class, ex -> {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 03a2b75..990a2d4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -298,6 +298,10 @@
return mIsManualDnd;
}
+ public boolean isEnabled() {
+ return mRule.isEnabled();
+ }
+
public boolean isActive() {
return mStatus == Status.ENABLED_AND_ACTIVE;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
index eb33a7a..e78b8a7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
@@ -19,21 +19,18 @@
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothCsipSetCoordinator
import android.bluetooth.BluetoothDevice
-import android.bluetooth.BluetoothLeBroadcast
-import android.bluetooth.BluetoothLeBroadcastMetadata
import android.bluetooth.BluetoothProfile
import android.bluetooth.BluetoothVolumeControl
import android.content.ContentResolver
-import android.content.Context
import android.database.ContentObserver
import android.provider.Settings
import androidx.annotation.IntRange
import com.android.internal.util.ConcurrentUtils
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.bluetooth.onBroadcastStartedOrStopped
import com.android.settingslib.bluetooth.onProfileConnectionStateChanged
import com.android.settingslib.bluetooth.onSourceConnectedOrRemoved
-import com.android.settingslib.flags.Flags
import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
import kotlin.coroutines.CoroutineContext
@@ -41,10 +38,10 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
@@ -85,63 +82,18 @@
@OptIn(ExperimentalCoroutinesApi::class)
class AudioSharingRepositoryImpl(
- private val context: Context,
private val contentResolver: ContentResolver,
- private val btManager: LocalBluetoothManager?,
+ private val btManager: LocalBluetoothManager,
private val coroutineScope: CoroutineScope,
private val backgroundCoroutineContext: CoroutineContext,
) : AudioSharingRepository {
override val inAudioSharing: Flow<Boolean> =
- if (Flags.enableLeAudioSharing()) {
- btManager?.profileManager?.leAudioBroadcastProfile?.let { leBroadcast ->
- callbackFlow {
- val listener =
- object : BluetoothLeBroadcast.Callback {
- override fun onBroadcastStarted(reason: Int, broadcastId: Int) {
- launch { send(isBroadcasting()) }
- }
-
- override fun onBroadcastStartFailed(reason: Int) {
- launch { send(isBroadcasting()) }
- }
-
- override fun onBroadcastStopped(reason: Int, broadcastId: Int) {
- launch { send(isBroadcasting()) }
- }
-
- override fun onBroadcastStopFailed(reason: Int) {
- launch { send(isBroadcasting()) }
- }
-
- override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
-
- override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
-
- override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
-
- override fun onBroadcastUpdateFailed(
- reason: Int,
- broadcastId: Int
- ) {}
-
- override fun onBroadcastMetadataChanged(
- broadcastId: Int,
- metadata: BluetoothLeBroadcastMetadata
- ) {}
- }
-
- leBroadcast.registerServiceCallBack(
- ConcurrentUtils.DIRECT_EXECUTOR,
- listener,
- )
- awaitClose { leBroadcast.unregisterServiceCallBack(listener) }
- }
- .onStart { emit(isBroadcasting()) }
- .flowOn(backgroundCoroutineContext)
- } ?: flowOf(false)
- } else {
- flowOf(false)
- }
+ btManager.profileManager.leAudioBroadcastProfile?.let { broadcast ->
+ broadcast.onBroadcastStartedOrStopped
+ .map { isBroadcasting() }
+ .onStart { emit(isBroadcasting()) }
+ .flowOn(backgroundCoroutineContext)
+ } ?: flowOf(false)
private val primaryChange: Flow<Unit> = callbackFlow {
val callback =
@@ -158,34 +110,24 @@
}
override val secondaryGroupId: StateFlow<Int> =
- if (Flags.volumeDialogAudioSharingFix()) {
- merge(
- btManager
- ?.profileManager
- ?.leAudioBroadcastAssistantProfile
- ?.onSourceConnectedOrRemoved
- ?.map { getSecondaryGroupId() } ?: emptyFlow(),
- btManager
- ?.eventManager
- ?.onProfileConnectionStateChanged
- ?.filter { profileConnection ->
- profileConnection.state == BluetoothAdapter.STATE_DISCONNECTED &&
- profileConnection.bluetoothProfile ==
- BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
- }
- ?.map { getSecondaryGroupId() } ?: emptyFlow(),
- primaryChange.map { getSecondaryGroupId() })
- .onStart { emit(getSecondaryGroupId()) }
- .distinctUntilChanged()
- .flowOn(backgroundCoroutineContext)
- } else {
- emptyFlow()
- }
+ merge(
+ btManager.profileManager.leAudioBroadcastAssistantProfile
+ ?.onSourceConnectedOrRemoved
+ ?.map { getSecondaryGroupId() } ?: emptyFlow(),
+ btManager.eventManager.onProfileConnectionStateChanged
+ .filter { profileConnection ->
+ profileConnection.state == BluetoothAdapter.STATE_DISCONNECTED &&
+ profileConnection.bluetoothProfile ==
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+ }
+ .map { getSecondaryGroupId() },
+ primaryChange.map { getSecondaryGroupId() })
+ .onStart { emit(getSecondaryGroupId()) }
+ .flowOn(backgroundCoroutineContext)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), getSecondaryGroupId())
override val volumeMap: StateFlow<GroupIdToVolumes> =
- if (Flags.volumeDialogAudioSharingFix()) {
- btManager?.profileManager?.volumeControlProfile?.let { volumeControl ->
+ (btManager.profileManager.volumeControlProfile?.let { volumeControl ->
inAudioSharing.flatMapLatest { isSharing ->
if (isSharing) {
callbackFlow {
@@ -210,50 +152,53 @@
.runningFold(emptyMap<Int, Int>()) { acc, value ->
val groupId =
BluetoothUtils.getGroupId(
- btManager.cachedDeviceManager?.findDevice(value.first))
+ btManager.cachedDeviceManager.findDevice(value.first))
if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
acc + Pair(groupId, value.second)
} else {
acc
}
}
- .distinctUntilChanged()
.flowOn(backgroundCoroutineContext)
} else {
emptyFlow()
}
}
- } ?: emptyFlow()
- } else {
- emptyFlow()
- }
- .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyMap())
+ } ?: emptyFlow())
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyMap())
override suspend fun setSecondaryVolume(
@IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
volume: Int
) {
withContext(backgroundCoroutineContext) {
- if (Flags.volumeDialogAudioSharingFix()) {
- btManager?.profileManager?.volumeControlProfile?.let {
- // Find secondary headset and set volume.
- val cachedDevice =
- BluetoothUtils.getSecondaryDeviceForBroadcast(context, btManager)
- if (cachedDevice != null) {
- it.setDeviceVolume(cachedDevice.device, volume, /* isGroupOp= */ true)
- }
+ btManager.profileManager.volumeControlProfile?.let {
+ // Find secondary headset and set volume.
+ val cachedDevice =
+ BluetoothUtils.getSecondaryDeviceForBroadcast(contentResolver, btManager)
+ if (cachedDevice != null) {
+ it.setDeviceVolume(cachedDevice.device, volume, /* isGroupOp= */ true)
}
}
}
}
- private fun isBroadcasting(): Boolean {
- return Flags.enableLeAudioSharing() &&
- (btManager?.profileManager?.leAudioBroadcastProfile?.isEnabled(null) ?: false)
- }
+ private fun isBroadcasting(): Boolean =
+ btManager.profileManager.leAudioBroadcastProfile?.isEnabled(null) ?: false
- private fun getSecondaryGroupId(): Int {
- return BluetoothUtils.getGroupId(
- BluetoothUtils.getSecondaryDeviceForBroadcast(context, btManager))
- }
+ private fun getSecondaryGroupId(): Int =
+ BluetoothUtils.getGroupId(
+ BluetoothUtils.getSecondaryDeviceForBroadcast(contentResolver, btManager))
+}
+
+class AudioSharingRepositoryEmptyImpl : AudioSharingRepository {
+ override val inAudioSharing: Flow<Boolean> = flowOf(false)
+ override val secondaryGroupId: StateFlow<Int> =
+ MutableStateFlow(BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
+ override val volumeMap: StateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap())
+
+ override suspend fun setSecondaryVolume(
+ @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+ volume: Int
+ ) {}
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryEmptyImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryEmptyImplTest.kt
new file mode 100644
index 0000000..2601592
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryEmptyImplTest.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothLeBroadcastReceiveState
+import android.content.Context
+import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.BluetoothEventManager
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
+import com.android.settingslib.bluetooth.VolumeControlProfile
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AudioSharingRepositoryEmptyImplTest {
+ @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
+ @Mock private lateinit var btManager: LocalBluetoothManager
+
+ @Mock private lateinit var profileManager: LocalBluetoothProfileManager
+
+ @Mock private lateinit var broadcast: LocalBluetoothLeBroadcast
+
+ @Mock private lateinit var assistant: LocalBluetoothLeBroadcastAssistant
+
+ @Mock private lateinit var volumeControl: VolumeControlProfile
+
+ @Mock private lateinit var eventManager: BluetoothEventManager
+
+ @Mock private lateinit var deviceManager: CachedBluetoothDeviceManager
+
+ @Mock private lateinit var device1: BluetoothDevice
+
+ @Mock private lateinit var device2: BluetoothDevice
+
+ @Mock private lateinit var cachedDevice1: CachedBluetoothDevice
+
+ @Mock private lateinit var cachedDevice2: CachedBluetoothDevice
+
+ @Mock private lateinit var receiveState: BluetoothLeBroadcastReceiveState
+
+ private val testScope = TestScope()
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private lateinit var underTest: AudioSharingRepository
+
+ @Before
+ fun setup() {
+ `when`(btManager.profileManager).thenReturn(profileManager)
+ `when`(profileManager.leAudioBroadcastProfile).thenReturn(broadcast)
+ `when`(profileManager.leAudioBroadcastAssistantProfile).thenReturn(assistant)
+ `when`(profileManager.volumeControlProfile).thenReturn(volumeControl)
+ `when`(btManager.eventManager).thenReturn(eventManager)
+ `when`(btManager.cachedDeviceManager).thenReturn(deviceManager)
+ `when`(broadcast.isEnabled(null)).thenReturn(true)
+ `when`(cachedDevice1.groupId).thenReturn(TEST_GROUP_ID1)
+ `when`(cachedDevice1.device).thenReturn(device1)
+ `when`(deviceManager.findDevice(device1)).thenReturn(cachedDevice1)
+ `when`(cachedDevice2.groupId).thenReturn(TEST_GROUP_ID2)
+ `when`(cachedDevice2.device).thenReturn(device2)
+ `when`(deviceManager.findDevice(device2)).thenReturn(cachedDevice2)
+ `when`(receiveState.bisSyncState).thenReturn(arrayListOf(TEST_RECEIVE_STATE_CONTENT))
+ `when`(assistant.getAllSources(any())).thenReturn(listOf(receiveState))
+ underTest = AudioSharingRepositoryEmptyImpl()
+ }
+
+ @Test
+ fun inAudioSharing_returnFalse() {
+ testScope.runTest {
+ val states = mutableListOf<Boolean?>()
+ underTest.inAudioSharing.onEach { states.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ Truth.assertThat(states).containsExactly(false)
+ verify(broadcast, never()).registerServiceCallBack(any(), any())
+ verify(broadcast, never()).isEnabled(any())
+ }
+ }
+
+ @Test
+ fun secondaryGroupIdChange_returnFalse() {
+ testScope.runTest {
+ val groupIds = mutableListOf<Int?>()
+ underTest.secondaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ Truth.assertThat(groupIds).containsExactly(TEST_GROUP_ID_INVALID)
+ verify(assistant, never()).registerServiceCallBack(any(), any())
+ verify(eventManager, never()).registerCallback(any())
+ }
+ }
+
+ @Test
+ fun volumeMapChange_returnFalse() {
+ testScope.runTest {
+ val volumeMaps = mutableListOf<GroupIdToVolumes?>()
+ underTest.volumeMap.onEach { volumeMaps.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ Truth.assertThat(volumeMaps).containsExactly(emptyMap<Int, Int>())
+ verify(broadcast, never()).registerServiceCallBack(any(), any())
+ verify(volumeControl, never()).registerCallback(any(), any())
+ }
+ }
+
+ @Test
+ fun setSecondaryVolume_doNothing() {
+ testScope.runTest {
+ Settings.Secure.putInt(
+ context.contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID2)
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+ underTest.setSecondaryVolume(TEST_VOLUME1)
+
+ runCurrent()
+ verify(volumeControl, never()).setDeviceVolume(any(), anyInt(), anyBoolean())
+ }
+ }
+
+ private companion object {
+ const val TEST_GROUP_ID_INVALID = -1
+ const val TEST_GROUP_ID1 = 1
+ const val TEST_GROUP_ID2 = 2
+ const val TEST_RECEIVE_STATE_CONTENT = 1L
+ const val TEST_VOLUME1 = 10
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
index 000664d..94595d3 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
@@ -26,8 +26,6 @@
import android.content.ContentResolver
import android.content.Context
import android.database.ContentObserver
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings
import androidx.test.core.app.ApplicationProvider
@@ -43,7 +41,6 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
import com.android.settingslib.bluetooth.VolumeControlProfile
-import com.android.settingslib.flags.Flags
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -57,14 +54,12 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
+import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
@@ -100,8 +95,6 @@
@Mock private lateinit var receiveState: BluetoothLeBroadcastReceiveState
- @Mock private lateinit var contentResolver: ContentResolver
-
@Captor
private lateinit var broadcastCallbackCaptor: ArgumentCaptor<BluetoothLeBroadcast.Callback>
@@ -118,6 +111,7 @@
private val testScope = TestScope()
private val context: Context = ApplicationProvider.getApplicationContext()
+ @Spy private val contentResolver: ContentResolver = context.contentResolver
private lateinit var underTest: AudioSharingRepository
@Before
@@ -139,7 +133,6 @@
`when`(assistant.getAllSources(any())).thenReturn(listOf(receiveState))
underTest =
AudioSharingRepositoryImpl(
- context,
contentResolver,
btManager,
testScope.backgroundScope,
@@ -148,7 +141,6 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
fun audioSharingStateChange_emitValues() {
testScope.runTest {
val states = mutableListOf<Boolean?>()
@@ -164,21 +156,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- fun audioSharingFlagOff_returnFalse() {
- testScope.runTest {
- val states = mutableListOf<Boolean?>()
- underTest.inAudioSharing.onEach { states.add(it) }.launchIn(backgroundScope)
- runCurrent()
-
- Truth.assertThat(states).containsExactly(false)
- verify(broadcast, never()).registerServiceCallBack(any(), any())
- verify(broadcast, never()).isEnabled(any())
- }
- }
-
- @Test
- @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
fun secondaryGroupIdChange_emitValues() {
testScope.runTest {
val groupIds = mutableListOf<Int?>()
@@ -214,21 +191,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
- fun secondaryGroupIdChange_audioSharingFlagOff_returnFalse() {
- testScope.runTest {
- val groupIds = mutableListOf<Int?>()
- underTest.secondaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
- runCurrent()
-
- Truth.assertThat(groupIds).containsExactly(TEST_GROUP_ID_INVALID)
- verify(assistant, never()).registerServiceCallBack(any(), any())
- verify(eventManager, never()).registerCallback(any())
- }
- }
-
- @Test
- @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
fun volumeMapChange_emitValues() {
testScope.runTest {
val volumeMaps = mutableListOf<GroupIdToVolumes?>()
@@ -252,21 +214,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
- fun volumeMapChange_audioSharingFlagOff_returnFalse() {
- testScope.runTest {
- val volumeMaps = mutableListOf<GroupIdToVolumes?>()
- underTest.volumeMap.onEach { volumeMaps.add(it) }.launchIn(backgroundScope)
- runCurrent()
-
- Truth.assertThat(volumeMaps).isEmpty()
- verify(broadcast, never()).registerServiceCallBack(any(), any())
- verify(volumeControl, never()).registerCallback(any(), any())
- }
- }
-
- @Test
- @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
fun setSecondaryVolume_setValue() {
testScope.runTest {
Settings.Secure.putInt(
@@ -281,22 +228,6 @@
}
}
- @Test
- @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
- fun setSecondaryVolume_audioSharingFlagOff_doNothing() {
- testScope.runTest {
- Settings.Secure.putInt(
- context.contentResolver,
- BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
- TEST_GROUP_ID2)
- `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
- underTest.setSecondaryVolume(TEST_VOLUME1)
-
- runCurrent()
- verify(volumeControl, never()).setDeviceVolume(any(), anyInt(), anyBoolean())
- }
- }
-
private fun triggerAudioSharingStateChange(
type: TriggerType,
broadcastAction: BluetoothLeBroadcast.Callback.() -> Unit
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 28bf348..370d568 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -63,22 +63,15 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private CachedBluetoothDevice mCachedBluetoothDevice;
- @Mock
- private BluetoothDevice mBluetoothDevice;
- @Mock
- private AudioManager mAudioManager;
- @Mock
- private PackageManager mPackageManager;
- @Mock
- private LocalBluetoothLeBroadcast mBroadcast;
- @Mock
- private LocalBluetoothProfileManager mProfileManager;
- @Mock
- private LocalBluetoothManager mLocalBluetoothManager;
- @Mock
- private LocalBluetoothLeBroadcastAssistant mAssistant;
- @Mock
- private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState;
+
+ @Mock private BluetoothDevice mBluetoothDevice;
+ @Mock private AudioManager mAudioManager;
+ @Mock private PackageManager mPackageManager;
+ @Mock private LocalBluetoothLeBroadcast mBroadcast;
+ @Mock private LocalBluetoothProfileManager mProfileManager;
+ @Mock private LocalBluetoothManager mLocalBluetoothManager;
+ @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
+ @Mock private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState;
private Context mContext;
private static final String STRING_METADATA = "string_metadata";
@@ -87,13 +80,13 @@
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
private static final String CONTROL_METADATA =
- "<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + STRING_METADATA
+ "<HEARABLE_CONTROL_SLICE_WITH_WIDTH>"
+ + STRING_METADATA
+ "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager";
private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component";
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
@@ -108,20 +101,20 @@
@Test
public void getBtClassDrawableWithDescription_typePhone_returnPhoneDrawable() {
- when(mCachedBluetoothDevice.getBtClass().getMajorDeviceClass()).thenReturn(
- BluetoothClass.Device.Major.PHONE);
- final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(
- mContext, mCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.getBtClass().getMajorDeviceClass())
+ .thenReturn(BluetoothClass.Device.Major.PHONE);
+ final Pair<Drawable, String> pair =
+ BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedBluetoothDevice);
verify(mContext).getDrawable(com.android.internal.R.drawable.ic_phone);
}
@Test
public void getBtClassDrawableWithDescription_typeComputer_returnComputerDrawable() {
- when(mCachedBluetoothDevice.getBtClass().getMajorDeviceClass()).thenReturn(
- BluetoothClass.Device.Major.COMPUTER);
- final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(
- mContext, mCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.getBtClass().getMajorDeviceClass())
+ .thenReturn(BluetoothClass.Device.Major.COMPUTER);
+ final Pair<Drawable, String> pair =
+ BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedBluetoothDevice);
verify(mContext).getDrawable(com.android.internal.R.drawable.ic_bt_laptop);
}
@@ -136,133 +129,146 @@
@Test
public void getBtRainbowDrawableWithDescription_normalHeadset_returnAdaptiveIcon() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn("false".getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
+ .thenReturn("false".getBytes());
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mCachedBluetoothDevice.getAddress()).thenReturn("1f:aa:bb");
- assertThat(BluetoothUtils.getBtRainbowDrawableWithDescription(
- RuntimeEnvironment.application,
- mCachedBluetoothDevice).first).isInstanceOf(AdaptiveIcon.class);
+ assertThat(
+ BluetoothUtils.getBtRainbowDrawableWithDescription(
+ RuntimeEnvironment.application, mCachedBluetoothDevice)
+ .first)
+ .isInstanceOf(AdaptiveIcon.class);
}
@Test
public void getStringMetaData_hasMetaData_getCorrectMetaData() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON)).thenReturn(
- STRING_METADATA.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON))
+ .thenReturn(STRING_METADATA.getBytes());
- assertThat(BluetoothUtils.getStringMetaData(mBluetoothDevice,
- BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON)).isEqualTo(STRING_METADATA);
+ assertThat(
+ BluetoothUtils.getStringMetaData(
+ mBluetoothDevice, BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON))
+ .isEqualTo(STRING_METADATA);
}
@Test
public void getIntMetaData_hasMetaData_getCorrectMetaData() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
- INT_METADATA.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY))
+ .thenReturn(INT_METADATA.getBytes());
- assertThat(BluetoothUtils.getIntMetaData(mBluetoothDevice,
- BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY))
+ assertThat(
+ BluetoothUtils.getIntMetaData(
+ mBluetoothDevice, BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY))
.isEqualTo(Integer.parseInt(INT_METADATA));
}
@Test
public void getIntMetaData_invalidMetaData_getErrorCode() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(null);
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY))
+ .thenReturn(null);
- assertThat(BluetoothUtils.getIntMetaData(mBluetoothDevice,
- BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON))
+ assertThat(
+ BluetoothUtils.getIntMetaData(
+ mBluetoothDevice, BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON))
.isEqualTo(BluetoothUtils.META_INT_ERROR);
}
@Test
public void getBooleanMetaData_hasMetaData_getCorrectMetaData() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
- BOOL_METADATA.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
+ .thenReturn(BOOL_METADATA.getBytes());
- assertThat(BluetoothUtils.getBooleanMetaData(mBluetoothDevice,
- BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).isEqualTo(true);
+ assertThat(
+ BluetoothUtils.getBooleanMetaData(
+ mBluetoothDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
+ .isTrue();
}
@Test
public void getUriMetaData_hasMetaData_getCorrectMetaData() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_MAIN_ICON)).thenReturn(
- STRING_METADATA.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON))
+ .thenReturn(STRING_METADATA.getBytes());
- assertThat(BluetoothUtils.getUriMetaData(mBluetoothDevice,
- BluetoothDevice.METADATA_MAIN_ICON)).isEqualTo(Uri.parse(STRING_METADATA));
+ assertThat(
+ BluetoothUtils.getUriMetaData(
+ mBluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON))
+ .isEqualTo(Uri.parse(STRING_METADATA));
}
@Test
public void getUriMetaData_nullMetaData_getNullUri() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_MAIN_ICON)).thenReturn(null);
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON)).thenReturn(null);
- assertThat(BluetoothUtils.getUriMetaData(mBluetoothDevice,
- BluetoothDevice.METADATA_MAIN_ICON)).isNull();
+ assertThat(
+ BluetoothUtils.getUriMetaData(
+ mBluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON))
+ .isNull();
}
@Test
public void getControlUriMetaData_hasMetaData_returnsCorrectMetaData() {
- when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)).thenReturn(
- CONTROL_METADATA.getBytes());
+ when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(CONTROL_METADATA.getBytes());
- assertThat(BluetoothUtils.getControlUriMetaData(mBluetoothDevice)).isEqualTo(
- STRING_METADATA);
+ assertThat(BluetoothUtils.getControlUriMetaData(mBluetoothDevice))
+ .isEqualTo(STRING_METADATA);
+ }
+
+ @Test
+ public void getFastPairCustomizedField_hasMetaData_returnsCorrectMetaData() {
+ when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(CONTROL_METADATA.getBytes());
+
+ assertThat(
+ BluetoothUtils.getFastPairCustomizedField(
+ mBluetoothDevice, KEY_HEARABLE_CONTROL_SLICE))
+ .isEqualTo(STRING_METADATA);
}
@Test
public void isAdvancedDetailsHeader_untetheredHeadset_returnTrue() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
- BOOL_METADATA.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
+ .thenReturn(BOOL_METADATA.getBytes());
- assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isTrue();
}
@Test
public void isAdvancedDetailsHeader_deviceTypeUntetheredHeadset_returnTrue() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
- assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isTrue();
}
@Test
public void isAdvancedDetailsHeader_deviceTypeWatch_returnTrue() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
- assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isTrue();
}
@Test
public void isAdvancedDetailsHeader_deviceTypeStylus_returnTrue() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_STYLUS.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_STYLUS.getBytes());
- assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isTrue();
}
@Test
public void isAdvancedDetailsHeader_deviceTypeDefault_returnTrue() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_DEFAULT.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_DEFAULT.getBytes());
- assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isTrue();
}
@Test
public void isAdvancedDetailsHeader_noMetadata_returnFalse() {
- assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isFalse();
}
@Test
@@ -286,43 +292,39 @@
@Test
public void isAdvancedUntetheredDevice_untetheredHeadset_returnTrue() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
- BOOL_METADATA.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
+ .thenReturn(BOOL_METADATA.getBytes());
- assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isTrue();
}
@Test
public void isAdvancedUntetheredDevice_deviceTypeUntetheredHeadset_returnTrue() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
- assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isTrue();
}
@Test
public void isAdvancedUntetheredDevice_deviceTypeWatch_returnFalse() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
- assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isFalse();
}
@Test
public void isAdvancedUntetheredDevice_deviceTypeDefault_returnFalse() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_DEFAULT.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_DEFAULT.getBytes());
- assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isFalse();
}
@Test
public void isAdvancedUntetheredDevice_noMetadata_returnFalse() {
- assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isFalse();
}
@Test
@@ -344,8 +346,10 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(true);
+ assertThat(
+ BluetoothUtils.isAvailableMediaBluetoothDevice(
+ mCachedBluetoothDevice, mAudioManager))
+ .isTrue();
}
@Test
@@ -356,8 +360,10 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(false);
+ assertThat(
+ BluetoothUtils.isAvailableMediaBluetoothDevice(
+ mCachedBluetoothDevice, mAudioManager))
+ .isFalse();
}
@Test
@@ -368,8 +374,10 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(true);
+ assertThat(
+ BluetoothUtils.isAvailableMediaBluetoothDevice(
+ mCachedBluetoothDevice, mAudioManager))
+ .isTrue();
}
@Test
@@ -380,8 +388,10 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(true);
+ assertThat(
+ BluetoothUtils.isAvailableMediaBluetoothDevice(
+ mCachedBluetoothDevice, mAudioManager))
+ .isTrue();
}
@Test
@@ -391,8 +401,8 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(false);
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
+ .isFalse();
}
@Test
@@ -403,8 +413,8 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(true);
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
+ .isTrue();
}
@Test
@@ -415,8 +425,8 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(false);
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
+ .isFalse();
}
@Test
@@ -427,8 +437,8 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(false);
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
+ .isFalse();
}
@Test
@@ -439,58 +449,61 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(false);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(false);
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
+ .isFalse();
}
@Test
public void isExclusivelyManaged_hasNoManager_returnFalse() {
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- null);
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(null);
- assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice))
+ .isFalse();
}
@Test
public void isExclusivelyManaged_hasPackageName_packageNotInstalled_returnFalse()
throws Exception {
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager)
+ doThrow(new PackageManager.NameNotFoundException())
+ .when(mPackageManager)
.getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
- assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice))
+ .isFalse();
}
@Test
public void isExclusivelyManaged_hasComponentName_packageNotInstalled_returnFalse()
throws Exception {
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager)
+ doThrow(new PackageManager.NameNotFoundException())
+ .when(mPackageManager)
.getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
- assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice))
+ .isFalse();
}
@Test
public void isExclusivelyManaged_hasPackageName_packageNotEnabled_returnFalse()
- throws Exception {
+ throws Exception {
ApplicationInfo appInfo = new ApplicationInfo();
appInfo.enabled = false;
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doReturn(appInfo).when(mPackageManager).getApplicationInfo(
- TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
+ doReturn(appInfo)
+ .when(mPackageManager)
+ .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
- assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice))
+ .isFalse();
}
@Test
@@ -499,45 +512,48 @@
ApplicationInfo appInfo = new ApplicationInfo();
appInfo.enabled = false;
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doReturn(appInfo).when(mPackageManager).getApplicationInfo(
- TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
+ doReturn(appInfo)
+ .when(mPackageManager)
+ .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
- assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice))
+ .isFalse();
}
@Test
public void isExclusivelyManaged_hasPackageName_packageInstalledAndEnabled_returnTrue()
throws Exception {
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo(
- TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+ doReturn(new ApplicationInfo())
+ .when(mPackageManager)
+ .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
- assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice))
+ .isTrue();
}
@Test
public void isExclusivelyManaged_hasComponentName_packageInstalledAndEnabled_returnTrue()
throws Exception {
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo(
- TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+ doReturn(new ApplicationInfo())
+ .when(mPackageManager)
+ .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
- assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice))
+ .isTrue();
}
@Test
public void testIsBroadcasting_broadcastEnabled_returnTrue() {
when(mBroadcast.isEnabled(any())).thenReturn(true);
- assertThat(BluetoothUtils.isBroadcasting(mLocalBluetoothManager)).isEqualTo(true);
+ assertThat(BluetoothUtils.isBroadcasting(mLocalBluetoothManager)).isTrue();
}
@Test
@@ -553,9 +569,9 @@
when(mAssistant.getAllSources(any())).thenReturn(sourceList);
assertThat(
- BluetoothUtils.hasConnectedBroadcastSource(
- mCachedBluetoothDevice, mLocalBluetoothManager))
- .isEqualTo(true);
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isTrue();
}
@Test
@@ -565,7 +581,7 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isAvailableHearingDevice(mCachedBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAvailableHearingDevice(mCachedBluetoothDevice)).isTrue();
}
@Test
@@ -589,7 +605,8 @@
@Test
public void getSecondaryDeviceForBroadcast_errorState_returnNull() {
- assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext, mLocalBluetoothManager))
+ assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext.getContentResolver(),
+ mLocalBluetoothManager))
.isNull();
}
@@ -608,7 +625,8 @@
when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(ImmutableList.of(state));
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mBluetoothDevice));
- assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext, mLocalBluetoothManager))
+ assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext.getContentResolver(),
+ mLocalBluetoothManager))
.isNull();
}
@@ -637,7 +655,8 @@
when(mAssistant.getAllConnectedDevices())
.thenReturn(ImmutableList.of(mBluetoothDevice, bluetoothDevice));
- assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext, mLocalBluetoothManager))
+ assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext.getContentResolver(),
+ mLocalBluetoothManager))
.isEqualTo(mCachedBluetoothDevice);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
new file mode 100644
index 0000000..b5457c5
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth.devicesettings.data.repository
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreference
+import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreferenceState
+import com.android.settingslib.bluetooth.devicesettings.DeviceInfo
+import com.android.settingslib.bluetooth.devicesettings.DeviceSetting
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingItem
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingState
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
+import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsConfigProviderService
+import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsListener
+import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsProviderService
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class DeviceSettingRepositoryTest {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var cachedDevice: CachedBluetoothDevice
+ @Mock private lateinit var bluetoothDevice: BluetoothDevice
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var bluetoothAdapter: BluetoothAdapter
+ @Mock private lateinit var configService: IDeviceSettingsConfigProviderService.Stub
+ @Mock private lateinit var settingProviderService1: IDeviceSettingsProviderService.Stub
+ @Mock private lateinit var settingProviderService2: IDeviceSettingsProviderService.Stub
+ @Captor
+ private lateinit var metadataChangeCaptor:
+ ArgumentCaptor<BluetoothAdapter.OnMetadataChangedListener>
+
+ private lateinit var underTest: DeviceSettingRepository
+ private val testScope = TestScope()
+
+ @Before
+ fun setUp() {
+ `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+ `when`(cachedDevice.address).thenReturn(BLUETOOTH_ADDRESS)
+ `when`(
+ bluetoothDevice.getMetadata(
+ DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS
+ )
+ )
+ .thenReturn(BLUETOOTH_DEVICE_METADATA.toByteArray())
+
+ `when`(configService.queryLocalInterface(anyString())).thenReturn(configService)
+ `when`(settingProviderService1.queryLocalInterface(anyString()))
+ .thenReturn(settingProviderService1)
+ `when`(settingProviderService2.queryLocalInterface(anyString()))
+ .thenReturn(settingProviderService2)
+
+ `when`(context.bindService(any(), any(), anyInt())).then { input ->
+ val intent = input.getArgument<Intent?>(0)
+ val connection = input.getArgument<ServiceConnection>(1)
+
+ when (intent?.action) {
+ CONFIG_SERVICE_INTENT_ACTION ->
+ connection.onServiceConnected(
+ ComponentName(CONFIG_SERVICE_PACKAGE_NAME, CONFIG_SERVICE_CLASS_NAME),
+ configService,
+ )
+ SETTING_PROVIDER_SERVICE_INTENT_ACTION_1 ->
+ connection.onServiceConnected(
+ ComponentName(
+ SETTING_PROVIDER_SERVICE_PACKAGE_NAME_1,
+ SETTING_PROVIDER_SERVICE_CLASS_NAME_1
+ ),
+ settingProviderService1,
+ )
+ SETTING_PROVIDER_SERVICE_INTENT_ACTION_2 ->
+ connection.onServiceConnected(
+ ComponentName(
+ SETTING_PROVIDER_SERVICE_PACKAGE_NAME_2,
+ SETTING_PROVIDER_SERVICE_CLASS_NAME_2,
+ ),
+ settingProviderService2,
+ )
+ }
+ true
+ }
+ underTest =
+ DeviceSettingRepositoryImpl(
+ context,
+ bluetoothAdapter,
+ testScope.backgroundScope,
+ testScope.testScheduler,
+ )
+ }
+
+ @After
+ fun clean() {
+ DeviceSettingServiceConnection.services.clear()
+ }
+
+ @Test
+ fun getDeviceSettingsConfig_withMetadata_success() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+
+ val config = underTest.getDeviceSettingsConfig(cachedDevice)
+
+ assertThat(config).isSameInstanceAs(DEVICE_SETTING_CONFIG)
+ }
+ }
+
+ @Test
+ fun getDeviceSettingsConfig_waitMetadataChange_success() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ `when`(
+ bluetoothDevice.getMetadata(
+ DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS
+ )
+ )
+ .thenReturn("".toByteArray())
+
+ var config: DeviceSettingsConfig? = null
+ val job = launch { config = underTest.getDeviceSettingsConfig(cachedDevice) }
+ delay(1000)
+ verify(bluetoothAdapter)
+ .addOnMetadataChangedListener(
+ eq(bluetoothDevice),
+ any(),
+ metadataChangeCaptor.capture()
+ )
+ metadataChangeCaptor.value.onMetadataChanged(
+ bluetoothDevice,
+ DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ BLUETOOTH_DEVICE_METADATA.toByteArray(),
+ )
+ `when`(
+ bluetoothDevice.getMetadata(
+ DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS
+ )
+ )
+ .thenReturn(BLUETOOTH_DEVICE_METADATA.toByteArray())
+
+ job.join()
+ assertThat(config).isSameInstanceAs(DEVICE_SETTING_CONFIG)
+ }
+ }
+
+ @Test
+ fun getDeviceSettingsConfig_bindingServiceFail_returnNull() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ doReturn(false).`when`(context).bindService(any(), any(), anyInt())
+
+ val config = underTest.getDeviceSettingsConfig(cachedDevice)
+
+ assertThat(config).isNull()
+ }
+ }
+
+ @Test
+ fun getDeviceSettingList_success() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
+ input ->
+ input
+ .getArgument<IDeviceSettingsListener>(1)
+ .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
+ }
+ `when`(settingProviderService2.registerDeviceSettingsListener(any(), any())).then {
+ input ->
+ input
+ .getArgument<IDeviceSettingsListener>(1)
+ .onDeviceSettingsChanged(listOf(DEVICE_SETTING_2))
+ }
+ var settings: List<DeviceSetting>? = null
+
+ underTest
+ .getDeviceSettingList(cachedDevice)
+ .onEach { settings = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ assertThat(settings?.map { it.settingId })
+ .containsExactly(
+ DeviceSettingId.DEVICE_SETTING_ID_HEADER,
+ DeviceSettingId.DEVICE_SETTING_ID_ANC
+ )
+ assertThat(settings?.map { (it.preference as ActionSwitchPreference).title })
+ .containsExactly(
+ "title1",
+ "title2",
+ )
+ }
+ }
+
+ @Test
+ fun getDeviceSetting_oneServiceFailed_returnPartialResult() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
+ input ->
+ input
+ .getArgument<IDeviceSettingsListener>(1)
+ .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
+ }
+ var settings: List<DeviceSetting>? = null
+
+ underTest
+ .getDeviceSettingList(cachedDevice)
+ .onEach { settings = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ assertThat(settings?.map { it.settingId })
+ .containsExactly(
+ DeviceSettingId.DEVICE_SETTING_ID_HEADER,
+ )
+ assertThat(settings?.map { (it.preference as ActionSwitchPreference).title })
+ .containsExactly(
+ "title1",
+ )
+ }
+ }
+
+ @Test
+ fun getDeviceSetting_success() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
+ input ->
+ input
+ .getArgument<IDeviceSettingsListener>(1)
+ .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
+ }
+ var setting: DeviceSetting? = null
+
+ underTest
+ .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_HEADER)
+ .onEach { setting = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ assertThat(setting?.settingId).isEqualTo(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
+ assertThat((setting?.preference as ActionSwitchPreference).title).isEqualTo("title1")
+ }
+ }
+
+ @Test
+ fun updateDeviceSetting_success() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
+ input ->
+ input
+ .getArgument<IDeviceSettingsListener>(1)
+ .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
+ }
+
+ underTest.updateDeviceSettingState(
+ cachedDevice,
+ DeviceSettingId.DEVICE_SETTING_ID_HEADER,
+ ActionSwitchPreferenceState.Builder().build()
+ )
+ runCurrent()
+
+ verify(settingProviderService1)
+ .updateDeviceSettings(
+ DEVICE_INFO,
+ DeviceSettingState.Builder()
+ .setSettingId(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
+ .setPreferenceState(ActionSwitchPreferenceState.Builder().build())
+ .build()
+ )
+ }
+ }
+
+ private companion object {
+ const val BLUETOOTH_ADDRESS = "12:34:56:78"
+ const val CONFIG_SERVICE_PACKAGE_NAME = "com.android.fake.configservice"
+ const val CONFIG_SERVICE_CLASS_NAME = "com.android.fake.configservice.Service"
+ const val CONFIG_SERVICE_INTENT_ACTION = "com.android.fake.configservice.BIND"
+ const val SETTING_PROVIDER_SERVICE_PACKAGE_NAME_1 =
+ "com.android.fake.settingproviderservice1"
+ const val SETTING_PROVIDER_SERVICE_CLASS_NAME_1 =
+ "com.android.fake.settingproviderservice1.Service"
+ const val SETTING_PROVIDER_SERVICE_INTENT_ACTION_1 =
+ "com.android.fake.settingproviderservice1.BIND"
+ const val SETTING_PROVIDER_SERVICE_PACKAGE_NAME_2 =
+ "com.android.fake.settingproviderservice2"
+ const val SETTING_PROVIDER_SERVICE_CLASS_NAME_2 =
+ "com.android.fake.settingproviderservice2.Service"
+ const val SETTING_PROVIDER_SERVICE_INTENT_ACTION_2 =
+ "com.android.fake.settingproviderservice2.BIND"
+ const val BLUETOOTH_DEVICE_METADATA =
+ "<DEVICE_SETTINGS_CONFIG_PACKAGE_NAME>" +
+ CONFIG_SERVICE_PACKAGE_NAME +
+ "</DEVICE_SETTINGS_CONFIG_PACKAGE_NAME>" +
+ "<DEVICE_SETTINGS_CONFIG_CLASS>" +
+ CONFIG_SERVICE_CLASS_NAME +
+ "</DEVICE_SETTINGS_CONFIG_CLASS>" +
+ "<DEVICE_SETTINGS_CONFIG_ACTION>" +
+ CONFIG_SERVICE_INTENT_ACTION +
+ "</DEVICE_SETTINGS_CONFIG_ACTION>"
+ val DEVICE_INFO = DeviceInfo.Builder().setBluetoothAddress(BLUETOOTH_ADDRESS).build()
+
+ val DEVICE_SETTING_ITEM_1 =
+ DeviceSettingItem(
+ DeviceSettingId.DEVICE_SETTING_ID_HEADER,
+ SETTING_PROVIDER_SERVICE_PACKAGE_NAME_1,
+ SETTING_PROVIDER_SERVICE_CLASS_NAME_1,
+ SETTING_PROVIDER_SERVICE_INTENT_ACTION_1
+ )
+ val DEVICE_SETTING_ITEM_2 =
+ DeviceSettingItem(
+ DeviceSettingId.DEVICE_SETTING_ID_ANC,
+ SETTING_PROVIDER_SERVICE_PACKAGE_NAME_2,
+ SETTING_PROVIDER_SERVICE_CLASS_NAME_2,
+ SETTING_PROVIDER_SERVICE_INTENT_ACTION_2
+ )
+ val DEVICE_SETTING_1 =
+ DeviceSetting.Builder()
+ .setSettingId(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
+ .setPreference(
+ ActionSwitchPreference.Builder()
+ .setTitle("title1")
+ .setHasSwitch(true)
+ .setAllowedChangingState(true)
+ .build()
+ )
+ .build()
+ val DEVICE_SETTING_2 =
+ DeviceSetting.Builder()
+ .setSettingId(DeviceSettingId.DEVICE_SETTING_ID_ANC)
+ .setPreference(
+ ActionSwitchPreference.Builder()
+ .setTitle("title2")
+ .setHasSwitch(true)
+ .setAllowedChangingState(true)
+ .build()
+ )
+ .build()
+ val DEVICE_SETTING_CONFIG =
+ DeviceSettingsConfig(
+ listOf(DEVICE_SETTING_ITEM_1),
+ listOf(DEVICE_SETTING_ITEM_2),
+ "footer"
+ )
+ }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 04d30ed..0b5187c 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -65,6 +65,7 @@
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
<uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
<uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
+ <uses-permission android:name="android.permission.READ_DROPBOX_DATA" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
<uses-permission android:name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 2f90ccc..cfd8f635 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -258,6 +258,7 @@
"tests/src/**/systemui/statusbar/policy/LocationControllerImplTest.java",
"tests/src/**/systemui/statusbar/policy/RemoteInputViewTest.java",
"tests/src/**/systemui/statusbar/policy/SmartReplyViewTest.java",
+ "tests/src/**/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt",
"tests/src/**/systemui/statusbar/StatusBarStateControllerImplTest.kt",
"tests/src/**/systemui/theme/ThemeOverlayApplierTest.java",
"tests/src/**/systemui/touch/TouchInsetManagerTest.java",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index ba84287..c1e43c9 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -19,3 +19,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "action_bar_wrap_content"
+ namespace: "accessibility"
+ description: "Applies WRAP_CONTENT to the action bar in A11yMenu settings to better fit large fonts"
+ bug: "347911378"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index ab8f97a..c71ef83 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -26,6 +26,7 @@
import android.provider.Browser;
import android.provider.Settings;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.TextView;
import android.window.OnBackInvokedCallback;
@@ -35,6 +36,7 @@
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
+import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.R;
/**
@@ -60,6 +62,18 @@
((TextView) findViewById(R.id.action_bar_title)).setText(
getResources().getString(R.string.accessibility_menu_settings_name)
);
+ if (Flags.actionBarWrapContent()) {
+ setHeightWrapContent(findViewById(com.android.internal.R.id.action_bar));
+ setHeightWrapContent(findViewById(com.android.internal.R.id.action_bar_container));
+ }
+ }
+
+ private void setHeightWrapContent(View view) {
+ if (view != null) {
+ ViewGroup.LayoutParams params = view.getLayoutParams();
+ params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ view.setLayoutParams(params);
+ }
}
@Override
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index eb9d0ab..e4f27aa 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -601,6 +601,13 @@
}
flag {
+ name: "screenshot_ui_controller_refactor"
+ namespace: "systemui"
+ description: "Simplify and refactor ScreenshotController"
+ bug: "354711957"
+}
+
+flag {
name: "run_fingerprint_detect_on_dismissible_keyguard"
namespace: "systemui"
description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
@@ -1206,6 +1213,13 @@
}
flag {
+ name: "hubmode_fullscreen_vertical_swipe"
+ namespace: "systemui"
+ description: "Enables fullscreen vertical swiping in hub mode to bring up and down the bouncer and shade"
+ bug: "340177049"
+}
+
+flag {
namespace: "systemui"
name: "remove_update_listener_in_qs_icon_view_impl"
description: "Remove update listeners in QsIconViewImpl class to avoid memory leak."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index f73b6cd..9fd30b4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -33,7 +33,7 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
object Bouncer {
object Elements {
@@ -56,7 +56,7 @@
) : ComposableScene {
override val key = Scenes.Bouncer
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 35db9e0..8245cc5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -88,12 +88,12 @@
}
to(CommunalScenes.Communal) {
spec = tween(durationMillis = 1000)
- translate(Communal.Elements.Grid, Edge.Right)
+ translate(Communal.Elements.Grid, Edge.End)
timestampRange(startMillis = 167, endMillis = 334) { fade(AllElements) }
}
to(CommunalScenes.Blank) {
spec = tween(durationMillis = 1000)
- translate(Communal.Elements.Grid, Edge.Right)
+ translate(Communal.Elements.Grid, Edge.End)
timestampRange(endMillis = 167) {
fade(Communal.Elements.Grid)
fade(Communal.Elements.IndicationArea)
@@ -186,9 +186,7 @@
scene(
CommunalScenes.Blank,
userActions =
- mapOf(
- Swipe(SwipeDirection.Left, fromSource = Edge.Right) to CommunalScenes.Communal
- )
+ mapOf(Swipe(SwipeDirection.Start, fromSource = Edge.End) to CommunalScenes.Communal)
) {
// This scene shows nothing only allowing for transitions to the communal scene.
Box(modifier = Modifier.fillMaxSize())
@@ -197,11 +195,11 @@
val userActions =
if (glanceableHubBackGesture()) {
mapOf(
- Swipe(SwipeDirection.Right) to CommunalScenes.Blank,
+ Swipe(SwipeDirection.End) to CommunalScenes.Blank,
Back to CommunalScenes.Blank,
)
} else {
- mapOf(Swipe(SwipeDirection.Right) to CommunalScenes.Blank)
+ mapOf(Swipe(SwipeDirection.End) to CommunalScenes.Blank)
}
scene(CommunalScenes.Communal, userActions = userActions) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 1ce51af..8c38253 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -98,13 +98,7 @@
bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
)
- val bottomAreaPlaceable =
- bottomAreaMeasurable.measure(
- noMinConstraints.copy(
- maxHeight =
- (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
- )
- )
+ val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints)
val communalGridPlaceable =
communalGridMeasurable.measure(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 68e968f..e29e0fd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -42,6 +42,7 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
+import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -51,6 +52,7 @@
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -67,8 +69,10 @@
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Check
@@ -90,6 +94,7 @@
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
@@ -112,6 +117,11 @@
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.pointer.changedToUp
+import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
@@ -121,6 +131,7 @@
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
@@ -137,7 +148,9 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.times
+import androidx.compose.ui.util.fastAll
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.layout.WindowMetricsCalculator
@@ -156,6 +169,8 @@
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.util.DensityUtils.Companion.adjustedDp
+import com.android.systemui.communal.util.DensityUtils.Companion.scalingAdjustment
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.res.R
@@ -200,21 +215,72 @@
ObserveScrollEffect(gridState, viewModel)
- if (!viewModel.isEditMode) {
+ val context = LocalContext.current
+ val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
+ val screenWidth = windowMetrics.bounds.width()
+ val layoutDirection = LocalLayoutDirection.current
+
+ if (viewModel.isEditMode) {
+ ScrollOnNewWidgetAddedEffect(communalContent, gridState)
+ } else {
ScrollOnUpdatedLiveContentEffect(communalContent, gridState)
}
+ val nestedScrollConnection = remember {
+ object : NestedScrollConnection {
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ // Begin tracking nested scrolling
+ viewModel.onNestedScrolling()
+ return super.onPreScroll(available, source)
+ }
+ }
+ }
+
Box(
modifier =
modifier
.semantics { testTagsAsResourceId = true }
.testTag(COMMUNAL_HUB_TEST_TAG)
.fillMaxSize()
- .pointerInput(gridState, contentOffset, contentListState) {
+ .nestedScroll(nestedScrollConnection)
+ .pointerInput(layoutDirection, gridState, contentOffset, contentListState) {
+ awaitPointerEventScope {
+ while (true) {
+ var event = awaitFirstDown(requireUnconsumed = false)
+ // Reset touch on first event.
+ viewModel.onResetTouchState()
+
+ // Process down event in case it's consumed immediately
+ if (event.isConsumed) {
+ viewModel.onHubTouchConsumed()
+ }
+
+ do {
+ var event = awaitPointerEvent()
+ for (change in event.changes) {
+ if (change.isConsumed) {
+ // Signal touch consumption on any consumed event.
+ viewModel.onHubTouchConsumed()
+ }
+ }
+ } while (
+ !event.changes.fastAll {
+ it.changedToUp() || it.changedToUpIgnoreConsumed()
+ }
+ )
+ }
+ }
+
// If not in edit mode, don't allow selecting items.
if (!viewModel.isEditMode) return@pointerInput
observeTaps { offset ->
- val adjustedOffset = offset - contentOffset
+ // if RTL, flip offset direction from Left side to Right
+ val adjustedOffset =
+ Offset(
+ if (layoutDirection == LayoutDirection.Rtl) screenWidth - offset.x
+ else offset.x,
+ offset.y
+ ) - contentOffset
val index = firstIndexAtOffset(gridState, adjustedOffset)
val key = index?.let { keyAtIndexIfEditable(contentListState.list, index) }
viewModel.setSelectedKey(key)
@@ -232,7 +298,12 @@
// offset.
val adjustedOffset =
gridCoordinates?.let {
- offset - it.positionInWindow() - contentOffset
+ Offset(
+ if (layoutDirection == LayoutDirection.Rtl)
+ screenWidth - offset.x
+ else offset.x,
+ offset.y
+ ) - it.positionInWindow() - contentOffset
}
val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
@@ -283,6 +354,7 @@
viewModel = viewModel,
contentPadding = contentPadding,
contentOffset = contentOffset,
+ screenWidth = screenWidth,
setGridCoordinates = { gridCoordinates = it },
updateDragPositionForRemove = { offset ->
isPointerWithinEnabledRemoveButton(
@@ -481,6 +553,36 @@
}
}
+/** Observes communal content and scrolls to a newly added widget if any. */
+@Composable
+private fun ScrollOnNewWidgetAddedEffect(
+ communalContent: List<CommunalContentModel>,
+ gridState: LazyGridState,
+) {
+ val coroutineScope = rememberCoroutineScope()
+ val widgetKeys = remember { mutableListOf<String>() }
+
+ LaunchedEffect(communalContent) {
+ val oldWidgetKeys = widgetKeys.toList()
+ widgetKeys.clear()
+ widgetKeys.addAll(communalContent.filter { it.isWidgetContent() }.map { it.key })
+
+ // Do nothing if there is no new widget
+ val indexOfFirstNewWidget = widgetKeys.indexOfFirst { !oldWidgetKeys.contains(it) }
+ if (indexOfFirstNewWidget < 0) {
+ return@LaunchedEffect
+ }
+
+ // Scroll if the new widget is not visible
+ val lastVisibleItemIndex = gridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
+ if (lastVisibleItemIndex != null && indexOfFirstNewWidget > lastVisibleItemIndex) {
+ // Launching with a scope to prevent the job from being canceled in the case of a
+ // recomposition during scrolling
+ coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstNewWidget) }
+ }
+ }
+}
+
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun BoxScope.CommunalHubLazyGrid(
@@ -488,6 +590,7 @@
viewModel: BaseCommunalViewModel,
contentPadding: PaddingValues,
selectedKey: State<String?>,
+ screenWidth: Int,
contentOffset: Offset,
gridState: LazyGridState,
contentListState: ContentListState,
@@ -510,7 +613,15 @@
updateDragPositionForRemove = updateDragPositionForRemove
)
gridModifier =
- gridModifier.fillMaxSize().dragContainer(dragDropState, contentOffset, viewModel)
+ gridModifier
+ .fillMaxSize()
+ .dragContainer(
+ dragDropState,
+ LocalLayoutDirection.current,
+ screenWidth,
+ contentOffset,
+ viewModel
+ )
// for widgets dropped from other activities
val dragAndDropTargetState =
rememberDragAndDropTargetState(
@@ -604,11 +715,11 @@
Card(
modifier = Modifier.height(hubDimensions.GridHeight).padding(contentPadding),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
- border = BorderStroke(3.dp, colors.secondary),
- shape = RoundedCornerShape(size = 80.dp)
+ border = BorderStroke(3.adjustedDp, colors.secondary),
+ shape = RoundedCornerShape(size = 80.adjustedDp)
) {
Column(
- modifier = Modifier.fillMaxSize().padding(horizontal = 110.dp),
+ modifier = Modifier.fillMaxSize().padding(horizontal = 110.adjustedDp),
verticalArrangement =
Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
horizontalAlignment = Alignment.CenterHorizontally,
@@ -791,7 +902,7 @@
onClick = onClick,
colors =
ButtonDefaults.outlinedButtonColors(
- contentColor = colors.primary,
+ contentColor = colors.onPrimaryContainer,
),
border = BorderStroke(width = 2.0.dp, color = colors.primary),
contentPadding = Dimensions.ButtonPadding,
@@ -855,22 +966,22 @@
/** Creates an empty card used to highlight a particular spot on the grid. */
@Composable
fun HighlightedItem(modifier: Modifier = Modifier, alpha: Float = 1.0f) {
- val brush = SolidColor(LocalAndroidColorScheme.current.primaryFixed)
+ val brush = SolidColor(LocalAndroidColorScheme.current.primary)
Box(
modifier =
// drawBehind lets us draw outside the bounds of the widgets so that we don't need to
// resize grid items to account for the border.
modifier.drawBehind {
// 8dp of padding between the widget and the highlight on every side.
- val padding = 8.dp.toPx()
+ val padding = 8.adjustedDp.toPx()
drawRoundRect(
brush,
alpha = alpha,
topLeft = Offset(-padding, -padding),
size =
Size(width = size.width + padding * 2, height = size.height + padding * 2),
- cornerRadius = CornerRadius(37.dp.toPx()),
- style = Stroke(width = 3.dp.toPx())
+ cornerRadius = CornerRadius(37.adjustedDp.toPx()),
+ style = Stroke(width = 3.adjustedDp.toPx())
)
}
)
@@ -890,10 +1001,12 @@
containerColor = colors.primary,
contentColor = colors.onPrimary,
),
- shape = RoundedCornerShape(68.dp, 34.dp, 68.dp, 34.dp)
+ shape = RoundedCornerShape(68.adjustedDp, 34.adjustedDp, 68.adjustedDp, 34.adjustedDp)
) {
Column(
- modifier = Modifier.fillMaxSize().padding(vertical = 32.dp, horizontal = 50.dp),
+ modifier =
+ Modifier.fillMaxSize()
+ .padding(vertical = 32.adjustedDp, horizontal = 50.adjustedDp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
@@ -902,47 +1015,57 @@
contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
modifier = Modifier.size(Dimensions.IconSize).clearAndSetSemantics {},
)
- Spacer(modifier = Modifier.size(6.dp))
+ Spacer(modifier = Modifier.size(6.adjustedDp))
Text(
text = stringResource(R.string.cta_label_to_edit_widget),
style = MaterialTheme.typography.titleLarge,
fontSize = nonScalableTextSize(22.dp),
lineHeight = nonScalableTextSize(28.dp),
+ modifier = Modifier.verticalScroll(rememberScrollState()).weight(1F)
)
- Spacer(modifier = Modifier.size(16.dp))
+ Spacer(modifier = Modifier.size(16.adjustedDp))
Row(
- modifier = Modifier.fillMaxWidth().height(56.dp),
- horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally),
+ modifier = Modifier.fillMaxWidth().height(56.adjustedDp),
+ horizontalArrangement =
+ Arrangement.spacedBy(16.adjustedDp, Alignment.CenterHorizontally),
) {
- OutlinedButton(
- modifier = Modifier.fillMaxHeight(),
- colors =
- ButtonDefaults.buttonColors(
- contentColor = colors.onPrimary,
- ),
- border = BorderStroke(width = 1.0.dp, color = colors.primaryContainer),
- contentPadding = PaddingValues(26.dp, 8.dp),
- onClick = viewModel::onDismissCtaTile,
+ CompositionLocalProvider(
+ LocalDensity provides
+ Density(
+ LocalDensity.current.density,
+ LocalDensity.current.fontScale.coerceIn(0f, 1.25f)
+ )
) {
- Text(
- text = stringResource(R.string.cta_tile_button_to_dismiss),
- fontSize = nonScalableTextSize(14.dp),
- )
- }
- Button(
- modifier = Modifier.fillMaxHeight(),
- colors =
- ButtonDefaults.buttonColors(
- containerColor = colors.primaryContainer,
- contentColor = colors.onPrimaryContainer,
- ),
- contentPadding = PaddingValues(26.dp, 8.dp),
- onClick = viewModel::onOpenWidgetEditor
- ) {
- Text(
- text = stringResource(R.string.cta_tile_button_to_open_widget_editor),
- fontSize = nonScalableTextSize(14.dp),
- )
+ OutlinedButton(
+ modifier = Modifier.fillMaxHeight().weight(1F),
+ colors =
+ ButtonDefaults.buttonColors(
+ contentColor = colors.onPrimary,
+ ),
+ border = BorderStroke(width = 1.0.dp, color = colors.primaryContainer),
+ onClick = viewModel::onDismissCtaTile,
+ contentPadding = PaddingValues(0.dp, 0.dp, 0.dp, 0.dp),
+ ) {
+ Text(
+ text = stringResource(R.string.cta_tile_button_to_dismiss),
+ fontSize = 14.sp,
+ )
+ }
+ Button(
+ modifier = Modifier.fillMaxHeight().weight(1F),
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = colors.primaryContainer,
+ contentColor = colors.onPrimaryContainer,
+ ),
+ onClick = viewModel::onOpenWidgetEditor,
+ contentPadding = PaddingValues(0.dp, 0.dp, 0.dp, 0.dp),
+ ) {
+ Text(
+ text = stringResource(R.string.cta_tile_button_to_open_widget_editor),
+ fontSize = 14.sp,
+ )
+ }
}
}
}
@@ -993,6 +1116,11 @@
modifier =
modifier
.then(selectableModifier)
+ .thenIf(!viewModel.isEditMode && !model.inQuietMode) {
+ Modifier.pointerInput(Unit) {
+ observeTaps { viewModel.onTapWidget(model.componentName, model.priority) }
+ }
+ }
.thenIf(!viewModel.isEditMode && model.inQuietMode) {
Modifier.pointerInput(Unit) {
// consume tap to prevent the child view from triggering interactions with
@@ -1102,11 +1230,11 @@
visible = visible,
enter = fadeIn(),
exit = fadeOut(),
- modifier = modifier.padding(16.dp),
+ modifier = modifier.padding(16.adjustedDp),
) {
FilledIconButton(
- shape = RoundedCornerShape(16.dp),
- modifier = Modifier.size(48.dp),
+ shape = RoundedCornerShape(16.adjustedDp),
+ modifier = Modifier.size(48.adjustedDp),
colors =
IconButtonColors(
containerColor = colors.primary,
@@ -1119,7 +1247,7 @@
Icon(
imageVector = Icons.Outlined.Edit,
contentDescription = stringResource(id = R.string.edit_widget),
- modifier = Modifier.padding(12.dp)
+ modifier = Modifier.padding(12.adjustedDp)
)
}
}
@@ -1182,7 +1310,9 @@
modifier =
modifier.background(
MaterialTheme.colorScheme.surfaceVariant,
- RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+ RoundedCornerShape(
+ dimensionResource(system_app_widget_background_radius) * scalingAdjustment
+ )
),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
@@ -1317,7 +1447,7 @@
private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingInPx {
return with(LocalDensity.current) {
ContentPaddingInPx(
- start = paddingValues.calculateLeftPadding(LayoutDirection.Ltr).toPx(),
+ start = paddingValues.calculateStartPadding(LocalLayoutDirection.current).toPx(),
top = paddingValues.calculateTopPadding().toPx()
)
}
@@ -1363,11 +1493,11 @@
val GridTopSpacing: Dp
get() {
if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- return 114.dp
+ return 114.adjustedDp
} else {
val windowMetrics =
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
- val screenHeight = with(density) { windowMetrics.bounds.height().toDp() }
+ val screenHeight = with(density) { windowMetrics.bounds.height().adjustedDp }
return (screenHeight - CardHeightFull) / 2
}
@@ -1376,26 +1506,47 @@
val GridHeight = CardHeightFull + GridTopSpacing
companion object {
- val CardHeightFull = 530.dp
- val ItemSpacing = 50.dp
- val CardHeightHalf = (CardHeightFull - ItemSpacing) / 2
- val CardHeightThird = (CardHeightFull - (2 * ItemSpacing)) / 3
- val CardWidth = 360.dp
- val CardOutlineWidth = 3.dp
- val Spacing = ItemSpacing / 2
+ val CardHeightFull
+ get() = 530.adjustedDp
+
+ val ItemSpacing
+ get() = 50.adjustedDp
+
+ val CardHeightHalf
+ get() = (CardHeightFull - ItemSpacing) / 2
+
+ val CardHeightThird
+ get() = (CardHeightFull - (2 * ItemSpacing)) / 3
+
+ val CardWidth
+ get() = 360.adjustedDp
+
+ val CardOutlineWidth
+ get() = 3.adjustedDp
+
+ val Spacing
+ get() = ItemSpacing / 2
// The sizing/padding of the toolbar in glanceable hub edit mode
- val ToolbarPaddingTop = 27.dp
- val ToolbarPaddingHorizontal = ItemSpacing
- val ToolbarButtonPaddingHorizontal = 24.dp
- val ToolbarButtonPaddingVertical = 16.dp
+ val ToolbarPaddingTop
+ get() = 27.adjustedDp
+
+ val ToolbarPaddingHorizontal
+ get() = ItemSpacing
+
+ val ToolbarButtonPaddingHorizontal
+ get() = 24.adjustedDp
+
+ val ToolbarButtonPaddingVertical
+ get() = 16.adjustedDp
+
val ButtonPadding =
PaddingValues(
vertical = ToolbarButtonPaddingVertical,
horizontal = ToolbarButtonPaddingHorizontal,
)
- val IconSize = 40.dp
- val SlideOffsetY = 30.dp
+ val IconSize = 40.adjustedDp
+ val SlideOffsetY = 30.adjustedDp
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index 94018bb..5886d7d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -30,8 +30,8 @@
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/** The communal scene shows glanceable hub when the device is locked and docked. */
@@ -45,10 +45,10 @@
) : ComposableScene {
override val key = Scenes.Communal
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
MutableStateFlow<Map<UserAction, UserActionResult>>(
mapOf(
- Swipe(SwipeDirection.Right) to UserActionResult(Scenes.Lockscreen),
+ Swipe(SwipeDirection.End) to UserActionResult(Scenes.Lockscreen),
)
)
.asStateFlow()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 07898b0..20ee131 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -37,7 +37,9 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
@@ -47,6 +49,9 @@
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
+private fun Float.directional(origin: LayoutDirection, current: LayoutDirection): Float =
+ if (origin == current) this else -this
+
@Composable
fun rememberGridDragDropState(
gridState: LazyGridState,
@@ -113,14 +118,24 @@
*
* @return {@code True} if dragging a grid item, {@code False} otherwise.
*/
- internal fun onDragStart(offset: Offset, contentOffset: Offset): Boolean {
+ internal fun onDragStart(
+ offset: Offset,
+ screenWidth: Int,
+ layoutDirection: LayoutDirection,
+ contentOffset: Offset
+ ): Boolean {
+ val normalizedOffset =
+ Offset(
+ if (layoutDirection == LayoutDirection.Ltr) offset.x else screenWidth - offset.x,
+ offset.y
+ )
state.layoutInfo.visibleItemsInfo
.filter { item -> contentListState.isItemEditable(item.index) }
// grid item offset is based off grid content container so we need to deduct
// before content padding from the initial pointer position
- .firstItemAtOffset(offset - contentOffset)
+ .firstItemAtOffset(normalizedOffset - contentOffset)
?.apply {
- dragStartPointerOffset = offset - this.offset.toOffset()
+ dragStartPointerOffset = normalizedOffset - this.offset.toOffset()
draggingItemIndex = index
draggingItemInitialOffset = this.offset.toOffset()
return true
@@ -145,8 +160,10 @@
dragStartPointerOffset = Offset.Zero
}
- internal fun onDrag(offset: Offset) {
- draggingItemDraggedDelta += offset
+ internal fun onDrag(offset: Offset, layoutDirection: LayoutDirection) {
+ // Adjust offset to match the layout direction
+ draggingItemDraggedDelta +=
+ Offset(offset.x.directional(LayoutDirection.Ltr, layoutDirection), offset.y)
val draggingItem = draggingItemLayoutInfo ?: return
val startOffset = draggingItem.offset.toOffset() + draggingItemOffset
@@ -213,6 +230,8 @@
fun Modifier.dragContainer(
dragDropState: GridDragDropState,
+ layoutDirection: LayoutDirection,
+ screenWidth: Int,
contentOffset: Offset,
viewModel: BaseCommunalViewModel,
): Modifier {
@@ -221,10 +240,17 @@
detectDragGesturesAfterLongPress(
onDrag = { change, offset ->
change.consume()
- dragDropState.onDrag(offset = offset)
+ dragDropState.onDrag(offset, layoutDirection)
},
onDragStart = { offset ->
- if (dragDropState.onDragStart(offset, contentOffset)) {
+ if (
+ dragDropState.onDragStart(
+ offset,
+ screenWidth,
+ layoutDirection,
+ contentOffset
+ )
+ ) {
viewModel.onReorderWidgetStart()
}
},
@@ -262,10 +288,12 @@
targetValue = if (dragDropState.isDraggingToRemove) 0.5f else 1f,
label = "DraggableItemAlpha"
)
+ val direction = LocalLayoutDirection.current
val draggingModifier =
if (dragging) {
Modifier.graphicsLayer {
- translationX = dragDropState.draggingItemOffset.x
+ translationX =
+ dragDropState.draggingItemOffset.x.directional(LayoutDirection.Ltr, direction)
translationY = dragDropState.draggingItemOffset.y
alpha = itemAlpha
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index c241f9c..b077e18 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -30,7 +30,7 @@
import com.android.systemui.scene.ui.composable.ComposableScene
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
/** The lock screen scene shows when the device is locked. */
@SysUISingleton
@@ -42,7 +42,7 @@
) : ComposableScene {
override val key = Scenes.Lockscreen
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 7400711..a9e63c6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -31,6 +31,7 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
+import com.android.compose.modifiers.thenIf
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
@@ -94,9 +95,12 @@
with(topAreaSection) {
DefaultClockLayout(
modifier =
- Modifier.graphicsLayer {
- translationX = unfoldTranslations.start
- }
+ Modifier.thenIf(isShadeLayoutWide) {
+ Modifier.fillMaxWidth(0.5f)
+ }
+ .graphicsLayer {
+ translationX = unfoldTranslations.start
+ }
)
}
if (isShadeLayoutWide) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 86639fa..6feaf6d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -32,6 +32,7 @@
import androidx.core.content.res.ResourcesCompat
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -55,6 +56,7 @@
private val vibratorHelper: VibratorHelper,
private val indicationController: KeyguardIndicationController,
private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
+ private val shortcutsLogger: KeyguardQuickAffordancesLogger,
) {
/**
* Renders a single lockscreen shortcut.
@@ -98,7 +100,7 @@
) {
MovableElement(
key = IndicationAreaElementKey,
- modifier = modifier.shortcutPadding(),
+ modifier = modifier.indicationAreaPadding(),
) {
content {
IndicationArea(
@@ -162,6 +164,7 @@
transitionAlpha,
falsingManager,
vibratorHelper,
+ shortcutsLogger,
) {
indicationController.showTransientIndication(it)
}
@@ -210,6 +213,11 @@
)
.padding(bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset))
}
+
+ @Composable
+ private fun Modifier.indicationAreaPadding(): Modifier {
+ return this.padding(bottom = dimensionResource(R.dimen.keyguard_indication_margin_bottom))
+ }
}
private val StartButtonElementKey = ElementKey("StartButton")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
index 0bd0809..7b497e8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
@@ -19,6 +19,7 @@
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementScenePicker
import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutState
import com.android.compose.animation.scene.TransitionState
import com.android.systemui.scene.shared.model.Scenes
@@ -45,22 +46,28 @@
shouldElevateMedia(transition) -> {
Scenes.Shade
}
-
- // TODO: 345467290 - update with the actual scene picking
+ transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Communal) -> {
+ Scenes.Lockscreen
+ }
transition.isTransitioningBetween(Scenes.QuickSettings, Scenes.Shade) -> {
Scenes.QuickSettings
}
-
- // TODO: 340216785 - update with the actual scene picking
- else -> pickSingleSceneIn(scenes, transition, element)
+ else -> {
+ when {
+ scenes.contains(transition.toScene) -> transition.toScene
+ scenes.contains(transition.fromScene) -> transition.fromScene
+ else -> null
+ }
+ }
}
}
/** Returns true when the media should be laid on top of the rest for the given [transition]. */
- fun shouldElevateMedia(transition: TransitionState.Transition?): Boolean {
- if (transition == null) {
- return false
- }
+ fun shouldElevateMedia(transition: TransitionState.Transition): Boolean {
return transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade)
}
}
+
+fun MediaScenePicker.shouldElevateMedia(layoutState: SceneTransitionLayoutState): Boolean {
+ return layoutState.currentTransition?.let { shouldElevateMedia(it) } ?: false
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index db98bc8f..7159def 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -44,7 +44,7 @@
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
@SysUISingleton
class NotificationsShadeScene
@@ -64,7 +64,7 @@
override val key = Scenes.NotificationsShade
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
sceneViewModel.destinationScenes
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 3cf8e70..2800eee 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -69,6 +69,8 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneDpAsState
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.modifiers.thenIf
@@ -79,7 +81,6 @@
import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
@@ -109,16 +110,13 @@
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.Flow
/** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
@SysUISingleton
class QuickSettingsScene
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
private val shadeSession: SaveableSession,
private val notificationStackScrollView: Lazy<NotificationScrollView>,
private val viewModel: QuickSettingsSceneViewModel,
@@ -131,12 +129,8 @@
) : ComposableScene {
override val key = Scenes.QuickSettings
- override val destinationScenes =
- viewModel.destinationScenes.stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = emptyMap(),
- )
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ viewModel.destinationScenes
@Composable
override fun SceneScope.Content(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index 422c9f6..f6d1283 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -60,7 +60,7 @@
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
@SysUISingleton
class QuickSettingsShadeScene
@@ -76,7 +76,7 @@
override val key = Scenes.QuickSettingsShade
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index a44041a..a9ddf84 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -36,7 +36,7 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
/**
* "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
@@ -52,7 +52,7 @@
) : ComposableScene {
override val key = Scenes.Gone
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 9c9e6c6..d5874d1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalComposeUiApi::class)
-
package com.android.systemui.scene.ui.composable
import androidx.compose.foundation.layout.Box
@@ -23,14 +21,13 @@
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
@@ -40,6 +37,7 @@
import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import kotlinx.coroutines.flow.collectLatest
/**
* Renders a container of a collection of "scenes" that the user can switch between using certain
@@ -62,19 +60,20 @@
fun SceneContainer(
viewModel: SceneContainerViewModel,
sceneByKey: Map<SceneKey, ComposableScene>,
+ initialSceneKey: SceneKey,
dataSourceDelegator: SceneDataSourceDelegator,
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
- val currentSceneKey: SceneKey by viewModel.currentScene.collectAsStateWithLifecycle()
val state: MutableSceneTransitionLayoutState = remember {
MutableSceneTransitionLayoutState(
- initialScene = currentSceneKey,
+ initialScene = initialSceneKey,
canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
transitions = SceneContainerTransitions,
enableInterruptions = false,
)
}
+ val currentSceneKey = state.transitionState.currentScene
DisposableEffect(state) {
val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
@@ -87,19 +86,32 @@
onDispose { viewModel.setTransitionState(null) }
}
- val userActionsBySceneKey: Map<SceneKey, Map<UserAction, UserActionResult>> =
- sceneByKey.values.associate { scene ->
- val userActions by scene.destinationScenes.collectAsStateWithLifecycle(emptyMap())
- val resolvedUserActions = viewModel.resolveSceneFamilies(userActions)
- scene.key to resolvedUserActions
+ val userActionsBySceneKey: MutableMap<SceneKey, Map<UserAction, UserActionResult>> = remember {
+ mutableStateMapOf()
+ }
+ LaunchedEffect(currentSceneKey) {
+ try {
+ sceneByKey[currentSceneKey]?.destinationScenes?.collectLatest { userActions ->
+ userActionsBySceneKey[currentSceneKey] = viewModel.resolveSceneFamilies(userActions)
+ }
+ } finally {
+ userActionsBySceneKey[currentSceneKey] = emptyMap()
}
+ }
Box(
modifier = Modifier.fillMaxSize(),
) {
SceneTransitionLayout(state = state, modifier = modifier.fillMaxSize()) {
sceneByKey.forEach { (sceneKey, composableScene) ->
- scene(key = sceneKey, userActions = checkNotNull(userActionsBySceneKey[sceneKey])) {
+ scene(
+ key = sceneKey,
+ userActions = userActionsBySceneKey.getOrDefault(sceneKey, emptyMap())
+ ) {
+ // Activate the scene.
+ LaunchedEffect(composableScene) { composableScene.activate() }
+
+ // Render the scene.
with(composableScene) {
this@scene.Content(
modifier = Modifier.element(sceneKey.rootElementKey).fillMaxSize(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
index f14ff76..2f8c248 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
@@ -16,10 +16,58 @@
package com.android.systemui.scene.ui.composable.transitions
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.compose.animation.scene.UserActionDistanceScope
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
+import com.android.systemui.media.controls.ui.composable.MediaScenePicker
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.shade.ui.composable.Shade
+import com.android.systemui.shade.ui.composable.ShadeHeader
+import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.goneToSplitShadeTransition(
durationScale: Double = 1.0,
) {
- toSplitShadeTransition(durationScale)
+ spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+ swipeSpec =
+ spring(
+ stiffness = Spring.StiffnessMediumLow,
+ visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
+ )
+ distance =
+ object : UserActionDistance {
+ override fun UserActionDistanceScope.absoluteDistance(
+ fromSceneSize: IntSize,
+ orientation: Orientation,
+ ): Float {
+ return fromSceneSize.height.toFloat() * 2 / 3f
+ }
+ }
+
+ fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }
+
+ fractionRange(start = .33f) {
+ val qsTranslation = ShadeHeader.Dimensions.CollapsedHeight * MediaScenePicker.SHADE_FRACTION
+ val qsExpansionDiff =
+ ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight
+ translate(MediaCarousel.Elements.Content, y = -(qsExpansionDiff + qsTranslation))
+ fade(MediaCarousel.Elements.Content)
+
+ fade(ShadeHeader.Elements.Clock)
+ fade(ShadeHeader.Elements.CollapsedContentStart)
+ fade(ShadeHeader.Elements.CollapsedContentEnd)
+ fade(ShadeHeader.Elements.PrivacyChip)
+ fade(QuickSettings.Elements.SplitShadeQuickSettings)
+ fade(QuickSettings.Elements.FooterActions)
+ fade(Notifications.Elements.NotificationScrim)
+ }
}
+
+private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt
index 0021bf5..5401936 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt
@@ -25,8 +25,8 @@
spec = tween(durationMillis = 500)
// Translate lockscreen to the left.
- translate(Scenes.Lockscreen.rootElementKey, Edge.Left)
+ translate(Scenes.Lockscreen.rootElementKey, Edge.Start)
// Translate communal from the right.
- translate(Scenes.Communal.rootElementKey, Edge.Right)
+ translate(Scenes.Communal.rootElementKey, Edge.End)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
index 70c343c..1486ea7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
@@ -16,10 +16,50 @@
package com.android.systemui.scene.ui.composable.transitions
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.compose.animation.scene.UserActionDistanceScope
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.shade.ui.composable.Shade
+import com.android.systemui.shade.ui.composable.ShadeHeader
+import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.lockscreenToSplitShadeTransition(
durationScale: Double = 1.0,
) {
- toSplitShadeTransition(durationScale = durationScale)
+ spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+ swipeSpec =
+ spring(
+ stiffness = Spring.StiffnessMediumLow,
+ visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
+ )
+ distance =
+ object : UserActionDistance {
+ override fun UserActionDistanceScope.absoluteDistance(
+ fromSceneSize: IntSize,
+ orientation: Orientation,
+ ): Float {
+ return fromSceneSize.height.toFloat() * 2 / 3f
+ }
+ }
+
+ fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }
+
+ fractionRange(start = .33f) {
+ fade(ShadeHeader.Elements.Clock)
+ fade(ShadeHeader.Elements.CollapsedContentStart)
+ fade(ShadeHeader.Elements.CollapsedContentEnd)
+ fade(ShadeHeader.Elements.PrivacyChip)
+ fade(QuickSettings.Elements.SplitShadeQuickSettings)
+ fade(QuickSettings.Elements.FooterActions)
+ fade(Notifications.Elements.NotificationScrim)
+ }
}
+
+private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt
deleted file mode 100644
index a8315c0..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.ui.composable.transitions
-
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.unit.IntSize
-import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
-import com.android.compose.animation.scene.UserActionDistanceScope
-import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.qs.ui.composable.QuickSettings
-import com.android.systemui.shade.ui.composable.Shade
-import com.android.systemui.shade.ui.composable.ShadeHeader
-import kotlin.time.Duration.Companion.milliseconds
-
-fun TransitionBuilder.toSplitShadeTransition(
- durationScale: Double = 1.0,
-) {
- spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- swipeSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
- distance =
- object : UserActionDistance {
- override fun UserActionDistanceScope.absoluteDistance(
- fromSceneSize: IntSize,
- orientation: Orientation,
- ): Float {
- return fromSceneSize.height.toFloat() * 2 / 3f
- }
- }
-
- fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }
-
- fractionRange(start = .33f) {
- fade(ShadeHeader.Elements.Clock)
- fade(ShadeHeader.Elements.CollapsedContentStart)
- fade(ShadeHeader.Elements.CollapsedContentEnd)
- fade(ShadeHeader.Elements.PrivacyChip)
- fade(QuickSettings.Elements.SplitShadeQuickSettings)
- fade(QuickSettings.Elements.FooterActions)
- fade(Notifications.Elements.NotificationScrim)
- }
-}
-
-private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 09414a9..18ca0f7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -60,6 +60,7 @@
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexScenePicker
@@ -81,10 +82,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.composable.MediaScenePicker
+import com.android.systemui.media.controls.ui.composable.shouldElevateMedia
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.media.dagger.MediaModule.QS_PANEL
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
import com.android.systemui.notifications.ui.composable.NotificationStackCutoffGuideline
@@ -109,7 +112,7 @@
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
object Shade {
object Elements {
@@ -148,12 +151,17 @@
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
private val statusBarIconController: StatusBarIconController,
private val mediaCarouselController: MediaCarouselController,
- @Named(QUICK_QS_PANEL) private val mediaHost: MediaHost,
+ @Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost,
+ @Named(QS_PANEL) private val qsMediaHost: MediaHost,
) : ComposableScene {
override val key = Scenes.Shade
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override suspend fun activate() {
+ viewModel.activate()
+ }
+
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
@@ -168,15 +176,20 @@
createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
statusBarIconController = statusBarIconController,
mediaCarouselController = mediaCarouselController,
- mediaHost = mediaHost,
+ qqsMediaHost = qqsMediaHost,
+ qsMediaHost = qsMediaHost,
modifier = modifier,
shadeSession = shadeSession,
)
init {
- mediaHost.expansion = MediaHostState.EXPANDED
- mediaHost.showsOnlyActiveMedia = true
- mediaHost.init(MediaHierarchyManager.LOCATION_QQS)
+ qqsMediaHost.expansion = MediaHostState.EXPANDED
+ qqsMediaHost.showsOnlyActiveMedia = true
+ qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
+
+ qsMediaHost.expansion = MediaHostState.EXPANDED
+ qsMediaHost.showsOnlyActiveMedia = false
+ qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
}
}
@@ -189,7 +202,8 @@
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
statusBarIconController: StatusBarIconController,
mediaCarouselController: MediaCarouselController,
- mediaHost: MediaHost,
+ qqsMediaHost: MediaHost,
+ qsMediaHost: MediaHost,
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
) {
@@ -204,7 +218,7 @@
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
mediaCarouselController = mediaCarouselController,
- mediaHost = mediaHost,
+ mediaHost = qqsMediaHost,
modifier = modifier,
shadeSession = shadeSession,
)
@@ -217,7 +231,7 @@
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
mediaCarouselController = mediaCarouselController,
- mediaHost = mediaHost,
+ mediaHost = qsMediaHost,
modifier = modifier,
shadeSession = shadeSession,
)
@@ -362,7 +376,7 @@
layout(constraints.maxWidth, constraints.maxHeight) {
val qsZIndex =
- if (MediaScenePicker.shouldElevateMedia(layoutState.currentTransition)) {
+ if (MediaScenePicker.shouldElevateMedia(layoutState)) {
1f
} else {
0f
@@ -471,17 +485,20 @@
val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha }
- Box(
- modifier =
- modifier
- .fillMaxSize()
- .element(Shade.Elements.BackgroundScrim)
- // Cannot set the alpha of the whole element to 0, because the mirror should be
- // in the QS column.
- .background(
- colorResource(R.color.shade_scrim_background_dark).copy(alpha = contentAlpha)
- )
- ) {
+ Box {
+ Box(
+ modifier =
+ modifier
+ .fillMaxSize()
+ .element(Shade.Elements.BackgroundScrim)
+ // Cannot set the alpha of the whole element to 0, because the mirror should be
+ // in the QS column.
+ .background(
+ colorResource(R.color.shade_scrim_background_dark)
+ .copy(alpha = contentAlpha)
+ )
+ )
+
Column(
modifier = Modifier.fillMaxSize(),
) {
@@ -541,11 +558,15 @@
squishiness = { tileSquishiness },
)
}
-
MediaCarousel(
isVisible = isMediaVisible,
mediaHost = mediaHost,
- modifier = Modifier.fillMaxWidth(),
+ modifier =
+ Modifier.fillMaxWidth().thenIf(
+ MediaScenePicker.shouldElevateMedia(layoutState)
+ ) {
+ Modifier.zIndex(1f)
+ },
carouselController = mediaCarouselController,
)
}
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..b329534 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
@@ -16,12 +16,16 @@
package com.android.compose.animation.scene
+import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation
import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
@@ -36,13 +40,11 @@
import androidx.compose.ui.input.pointer.util.VelocityTracker
import androidx.compose.ui.input.pointer.util.addPointerInputChange
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
-import androidx.compose.ui.node.DelegatableNode
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.PointerInputModifierNode
import androidx.compose.ui.node.currentValueOf
-import androidx.compose.ui.node.findNearestAncestor
import androidx.compose.ui.node.observeReads
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.unit.IntSize
@@ -51,6 +53,7 @@
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastFirstOrNull
import androidx.compose.ui.util.fastSumBy
+import com.android.compose.ui.util.SpaceVectorConverter
import kotlin.coroutines.cancellation.CancellationException
import kotlin.math.sign
import kotlinx.coroutines.coroutineScope
@@ -71,6 +74,7 @@
* dragged) and a second pointer is down and dragged. This is an implementation detail that might
* change in the future.
*/
+@VisibleForTesting
@Stable
internal fun Modifier.multiPointerDraggable(
orientation: Orientation,
@@ -78,6 +82,7 @@
startDragImmediately: (startedPosition: Offset) -> Boolean,
onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
+ dispatcher: NestedScrollDispatcher,
): Modifier =
this.then(
MultiPointerDraggableElement(
@@ -86,6 +91,7 @@
startDragImmediately,
onDragStarted,
swipeDetector,
+ dispatcher,
)
)
@@ -96,6 +102,7 @@
private val onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
private val swipeDetector: SwipeDetector,
+ private val dispatcher: NestedScrollDispatcher,
) : ModifierNodeElement<MultiPointerDraggableNode>() {
override fun create(): MultiPointerDraggableNode =
MultiPointerDraggableNode(
@@ -104,6 +111,7 @@
startDragImmediately = startDragImmediately,
onDragStarted = onDragStarted,
swipeDetector = swipeDetector,
+ dispatcher = dispatcher,
)
override fun update(node: MultiPointerDraggableNode) {
@@ -115,18 +123,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,
@@ -134,11 +130,13 @@
var onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
var swipeDetector: SwipeDetector = DefaultSwipeDetector,
+ private val dispatcher: NestedScrollDispatcher,
) :
DelegatingNode(),
PointerInputModifierNode,
CompositionLocalConsumerModifierNode,
- ObserverModifierNode {
+ ObserverModifierNode,
+ SpaceVectorConverter {
private val pointerInputHandler: suspend PointerInputScope.() -> Unit = { pointerInput() }
private val delegate = delegate(SuspendingPointerInputModifierNode(pointerInputHandler))
private val velocityTracker = VelocityTracker()
@@ -153,26 +151,22 @@
}
}
- private var _toFloat = orientation.toFunctionOffsetToFloat()
+ private var converter = SpaceVectorConverter(orientation)
- private fun Offset.toFloat(): Float = _toFloat(this)
+ override fun Offset.toFloat(): Float = with(converter) { this@toFloat.toFloat() }
- private fun Orientation.toFunctionOffsetToFloat(): (Offset) -> Float =
- when (this) {
- Orientation.Vertical -> {
- { it.y }
- }
- Orientation.Horizontal -> {
- { it.x }
- }
- }
+ override fun Velocity.toFloat(): Float = with(converter) { this@toFloat.toFloat() }
+
+ override fun Float.toOffset(): Offset = with(converter) { this@toOffset.toOffset() }
+
+ override fun Float.toVelocity(): Velocity = with(converter) { this@toVelocity.toVelocity() }
var orientation: Orientation = orientation
set(value) {
// Reset the pointer input whenever orientation changed.
if (value != field) {
field = value
- _toFloat = field.toFunctionOffsetToFloat()
+ converter = SpaceVectorConverter(value)
delegate.resetPointerInputHandler()
}
}
@@ -252,28 +246,32 @@
},
onDrag = { controller, change, amount ->
velocityTracker.addPointerInputChange(change)
- controller.onDrag(amount)
+ dispatchScrollEvents(
+ availableOnPreScroll = amount,
+ onScroll = { controller.onDrag(it) },
+ source = NestedScrollSource.UserInput,
+ )
},
onDragEnd = { controller ->
- val viewConfiguration = currentValueOf(LocalViewConfiguration)
- val maxVelocity =
- viewConfiguration.maximumFlingVelocity.let {
- Velocity(it, it)
- }
- val velocity = velocityTracker.calculateVelocity(maxVelocity)
- controller.onStop(
- velocity =
- when (orientation) {
- Orientation.Horizontal -> velocity.x
- Orientation.Vertical -> velocity.y
- },
- canChangeScene = true,
+ startFlingGesture(
+ initialVelocity =
+ currentValueOf(LocalViewConfiguration)
+ .maximumFlingVelocity
+ .let {
+ val maxVelocity = Velocity(it, it)
+ velocityTracker.calculateVelocity(maxVelocity)
+ }
+ .toFloat(),
+ onFling = { controller.onStop(it, canChangeScene = true) }
)
},
onDragCancel = { controller ->
- controller.onStop(velocity = 0f, canChangeScene = true)
+ startFlingGesture(
+ initialVelocity = 0f,
+ onFling = { controller.onStop(it, canChangeScene = true) }
+ )
},
- swipeDetector = swipeDetector
+ swipeDetector = swipeDetector,
)
} catch (exception: CancellationException) {
// If the coroutine scope is active, we can just restart the drag cycle.
@@ -288,6 +286,101 @@
}
/**
+ * Start a fling gesture in another CoroutineScope, this is to ensure that even when the pointer
+ * input scope is reset we will continue any coroutine scope that we started from these methods
+ * while the pointer input scope was active.
+ *
+ * Note: Inspired by [androidx.compose.foundation.gestures.ScrollableNode.onDragStopped]
+ */
+ private fun startFlingGesture(initialVelocity: Float, onFling: (velocity: Float) -> Float) {
+ // Note: [AwaitPointerEventScope] is annotated as @RestrictsSuspension, we need another
+ // CoroutineScope to run the fling gestures.
+ // We do not need to cancel this [Job], the source will take care of emitting an
+ // [onPostFling] before starting a new gesture.
+ dispatcher.coroutineScope.launch {
+ dispatchFlingEvents(availableOnPreFling = initialVelocity, onFling = onFling)
+ }
+ }
+
+ /**
+ * Use the nested scroll system to fire scroll events. This allows us to consume events from our
+ * ancestors during the pre-scroll and post-scroll phases.
+ *
+ * @param availableOnPreScroll amount available before the scroll, this can be partially
+ * consumed by our ancestors.
+ * @param onScroll function that returns the amount consumed during a scroll given the amount
+ * available after the [NestedScrollConnection.onPreScroll].
+ * @param source the source of the scroll event
+ * @return Total offset consumed.
+ */
+ private inline fun dispatchScrollEvents(
+ availableOnPreScroll: Float,
+ onScroll: (delta: Float) -> Float,
+ source: NestedScrollSource,
+ ): Float {
+ // PreScroll phase
+ val consumedByPreScroll =
+ dispatcher
+ .dispatchPreScroll(
+ available = availableOnPreScroll.toOffset(),
+ source = source,
+ )
+ .toFloat()
+
+ // Scroll phase
+ val availableOnScroll = availableOnPreScroll - consumedByPreScroll
+ val consumedBySelfScroll = onScroll(availableOnScroll)
+
+ // PostScroll phase
+ val availableOnPostScroll = availableOnScroll - consumedBySelfScroll
+ val consumedByPostScroll =
+ dispatcher
+ .dispatchPostScroll(
+ consumed = consumedBySelfScroll.toOffset(),
+ available = availableOnPostScroll.toOffset(),
+ source = source,
+ )
+ .toFloat()
+
+ return consumedByPreScroll + consumedBySelfScroll + consumedByPostScroll
+ }
+
+ /**
+ * Use the nested scroll system to fire fling events. This allows us to consume events from our
+ * ancestors during the pre-fling and post-fling phases.
+ *
+ * @param availableOnPreFling velocity available before the fling, this can be partially
+ * consumed by our ancestors.
+ * @param onFling function that returns the velocity consumed during the fling given the
+ * velocity available after the [NestedScrollConnection.onPreFling].
+ * @return Total velocity consumed.
+ */
+ private suspend inline fun dispatchFlingEvents(
+ availableOnPreFling: Float,
+ onFling: (velocity: Float) -> Float,
+ ): Float {
+ // PreFling phase
+ val consumedByPreFling =
+ dispatcher.dispatchPreFling(available = availableOnPreFling.toVelocity()).toFloat()
+
+ // Fling phase
+ val availableOnFling = availableOnPreFling - consumedByPreFling
+ val consumedBySelfFling = onFling(availableOnFling)
+
+ // PostFling phase
+ val availableOnPostFling = availableOnFling - consumedBySelfFling
+ val consumedByPostFling =
+ dispatcher
+ .dispatchPostFling(
+ consumed = consumedBySelfFling.toVelocity(),
+ available = availableOnPostFling.toVelocity(),
+ )
+ .toFloat()
+
+ return consumedByPreFling + consumedBySelfFling + consumedByPostFling
+ }
+
+ /**
* Detect drag gestures in the given [orientation].
*
* This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and
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/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index b8010f2..a2118b2 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -20,6 +20,7 @@
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
@@ -57,6 +58,7 @@
draggableHandler: DraggableHandlerImpl,
swipeDetector: SwipeDetector,
) : DelegatingNode(), PointerInputModifierNode {
+ private val dispatcher = NestedScrollDispatcher()
private val multiPointerDraggableNode =
delegate(
MultiPointerDraggableNode(
@@ -65,6 +67,7 @@
startDragImmediately = ::startDragImmediately,
onDragStarted = draggableHandler::onDragStarted,
swipeDetector = swipeDetector,
+ dispatcher = dispatcher,
)
)
@@ -93,7 +96,7 @@
)
init {
- delegate(nestedScrollModifierNode(nestedScrollHandlerImpl.connection, dispatcher = null))
+ delegate(nestedScrollModifierNode(nestedScrollHandlerImpl.connection, dispatcher))
delegate(ScrollBehaviorOwnerNode(draggableHandler.nestedScrollKey, nestedScrollHandlerImpl))
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index b98400a..2d37a0d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -28,6 +28,10 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerInputChange
@@ -37,6 +41,7 @@
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.coroutineScope
@@ -49,17 +54,22 @@
class MultiPointerDraggableTest {
@get:Rule val rule = createComposeRule()
+ private val emptyConnection = object : NestedScrollConnection {}
+ private val defaultDispatcher = NestedScrollDispatcher()
+
+ private fun Modifier.nestedScrollDispatcher() = nestedScroll(emptyConnection, defaultDispatcher)
+
private class SimpleDragController(
- val onDrag: () -> Unit,
- val onStop: () -> Unit,
+ val onDrag: (delta: Float) -> Unit,
+ val onStop: (velocity: Float) -> Unit,
) : DragController {
override fun onDrag(delta: Float): Float {
- onDrag()
+ onDrag.invoke(delta)
return delta
}
override fun onStop(velocity: Float, canChangeScene: Boolean): Float {
- onStop()
+ onStop.invoke(velocity)
return velocity
}
}
@@ -79,6 +89,7 @@
touchSlop = LocalViewConfiguration.current.touchSlop
Box(
Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
enabled = { enabled },
@@ -90,6 +101,7 @@
onStop = { stopped = true },
)
},
+ dispatcher = defaultDispatcher,
)
)
}
@@ -145,6 +157,7 @@
touchSlop = LocalViewConfiguration.current.touchSlop
Box(
Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
enabled = { true },
@@ -157,6 +170,7 @@
onStop = { stopped = true },
)
},
+ dispatcher = defaultDispatcher,
)
.pointerInput(Unit) {
coroutineScope {
@@ -217,6 +231,7 @@
touchSlop = LocalViewConfiguration.current.touchSlop
Box(
Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
enabled = { true },
@@ -228,6 +243,7 @@
onStop = { stopped = true },
)
},
+ dispatcher = defaultDispatcher,
)
) {
if (hasScrollable) {
@@ -335,6 +351,7 @@
touchSlop = LocalViewConfiguration.current.touchSlop
Box(
Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
enabled = { true },
@@ -346,6 +363,7 @@
onStop = { stopped = true },
)
},
+ dispatcher = defaultDispatcher,
)
) {
Box(
@@ -436,6 +454,7 @@
touchSlop = LocalViewConfiguration.current.touchSlop
Box(
Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
enabled = { true },
@@ -447,6 +466,7 @@
onStop = { verticalStopped = true },
)
},
+ dispatcher = defaultDispatcher,
)
.multiPointerDraggable(
orientation = Orientation.Horizontal,
@@ -459,6 +479,7 @@
onStop = { horizontalStopped = true },
)
},
+ dispatcher = defaultDispatcher,
)
)
}
@@ -539,6 +560,7 @@
touchSlop = LocalViewConfiguration.current.touchSlop
Box(
Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
enabled = { true },
@@ -557,6 +579,7 @@
onStop = { /* do nothing */ },
)
},
+ dispatcher = defaultDispatcher,
)
) {}
}
@@ -587,4 +610,113 @@
assertThat(started).isTrue()
}
+
+ @Test
+ fun multiPointerNestedScrollDispatcher() {
+ val size = 200f
+ val middle = Offset(size / 2f, size / 2f)
+ var touchSlop = 0f
+
+ var consumedOnPreScroll = 0f
+
+ var availableOnPreScroll = Float.MIN_VALUE
+ var availableOnPostScroll = Float.MIN_VALUE
+ var availableOnPreFling = Float.MIN_VALUE
+ var availableOnPostFling = Float.MIN_VALUE
+
+ var consumedOnDrag = 0f
+ var consumedOnDragStop = 0f
+
+ val connection =
+ object : NestedScrollConnection {
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ availableOnPreScroll = available.y
+ return Offset(0f, consumedOnPreScroll)
+ }
+
+ override fun onPostScroll(
+ consumed: Offset,
+ available: Offset,
+ source: NestedScrollSource
+ ): Offset {
+ availableOnPostScroll = available.y
+ return Offset.Zero
+ }
+
+ override suspend fun onPreFling(available: Velocity): Velocity {
+ availableOnPreFling = available.y
+ return Velocity.Zero
+ }
+
+ override suspend fun onPostFling(
+ consumed: Velocity,
+ available: Velocity
+ ): Velocity {
+ availableOnPostFling = available.y
+ return Velocity.Zero
+ }
+ }
+
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ Box(
+ Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScroll(connection)
+ .nestedScrollDispatcher()
+ .multiPointerDraggable(
+ orientation = Orientation.Vertical,
+ enabled = { true },
+ startDragImmediately = { false },
+ onDragStarted = { _, _, _ ->
+ SimpleDragController(
+ onDrag = { consumedOnDrag = it },
+ onStop = { consumedOnDragStop = it },
+ )
+ },
+ dispatcher = defaultDispatcher,
+ )
+ )
+ }
+
+ fun startDrag() {
+ rule.onRoot().performTouchInput {
+ down(middle)
+ moveBy(Offset(0f, touchSlop))
+ }
+ }
+
+ fun continueDrag() {
+ rule.onRoot().performTouchInput { moveBy(Offset(0f, touchSlop)) }
+ }
+
+ fun stopDrag() {
+ rule.onRoot().performTouchInput { up() }
+ }
+
+ startDrag()
+
+ continueDrag()
+ assertThat(availableOnPreScroll).isEqualTo(touchSlop)
+ assertThat(consumedOnDrag).isEqualTo(touchSlop)
+ assertThat(availableOnPostScroll).isEqualTo(0f)
+
+ // Parent node consumes half of the gesture
+ consumedOnPreScroll = touchSlop / 2f
+ continueDrag()
+ assertThat(availableOnPreScroll).isEqualTo(touchSlop)
+ assertThat(consumedOnDrag).isEqualTo(touchSlop / 2f)
+ assertThat(availableOnPostScroll).isEqualTo(0f)
+
+ // Parent node consumes the gesture
+ consumedOnPreScroll = touchSlop
+ continueDrag()
+ assertThat(availableOnPreScroll).isEqualTo(touchSlop)
+ assertThat(consumedOnDrag).isEqualTo(0f)
+ assertThat(availableOnPostScroll).isEqualTo(0f)
+
+ // Parent node can intercept the velocity on stop
+ stopDrag()
+ assertThat(availableOnPreFling).isEqualTo(consumedOnDragStop)
+ assertThat(availableOnPostFling).isEqualTo(0f)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
new file mode 100644
index 0000000..4850085
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.ambient.touch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.content.pm.UserInfo;
+import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Flags;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.scrim.ScrimController;
+import com.android.systemui.ambient.touch.scrim.ScrimManager;
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.FakeUserTracker;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+@DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
+public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
+ private KosmosJavaAdapter mKosmos;
+
+ @Mock
+ CentralSurfaces mCentralSurfaces;
+
+ @Mock
+ ScrimManager mScrimManager;
+
+ @Mock
+ ScrimController mScrimController;
+
+ @Mock
+ NotificationShadeWindowController mNotificationShadeWindowController;
+
+ @Mock
+ FlingAnimationUtils mFlingAnimationUtils;
+
+ @Mock
+ FlingAnimationUtils mFlingAnimationUtilsClosing;
+
+ @Mock
+ TouchHandler.TouchSession mTouchSession;
+
+ BouncerSwipeTouchHandler mTouchHandler;
+
+ @Mock
+ BouncerSwipeTouchHandler.ValueAnimatorCreator mValueAnimatorCreator;
+
+ @Mock
+ ValueAnimator mValueAnimator;
+
+ @Mock
+ BouncerSwipeTouchHandler.VelocityTrackerFactory mVelocityTrackerFactory;
+
+ @Mock
+ VelocityTracker mVelocityTracker;
+
+ @Mock
+ UiEventLogger mUiEventLogger;
+
+ @Mock
+ LockPatternUtils mLockPatternUtils;
+
+ @Mock
+ ActivityStarter mActivityStarter;
+
+ @Mock
+ CommunalViewModel mCommunalViewModel;
+
+ FakeUserTracker mUserTracker;
+
+ private static final float TOUCH_REGION = .3f;
+ private static final float MIN_BOUNCER_HEIGHT = .05f;
+
+ private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
+ private static final UserInfo CURRENT_USER_INFO = new UserInfo(
+ 10,
+ /* name= */ "user10",
+ /* flags= */ 0
+ );
+
+ @Before
+ public void setup() {
+ mKosmos = new KosmosJavaAdapter(this);
+ MockitoAnnotations.initMocks(this);
+ mUserTracker = new FakeUserTracker();
+ mTouchHandler = new BouncerSwipeTouchHandler(
+ mKosmos.getTestScope(),
+ mScrimManager,
+ Optional.of(mCentralSurfaces),
+ mNotificationShadeWindowController,
+ mValueAnimatorCreator,
+ mVelocityTrackerFactory,
+ mLockPatternUtils,
+ mUserTracker,
+ mCommunalViewModel,
+ mFlingAnimationUtils,
+ mFlingAnimationUtilsClosing,
+ TOUCH_REGION,
+ MIN_BOUNCER_HEIGHT,
+ mUiEventLogger,
+ mActivityStarter);
+
+ when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
+ when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
+ when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
+ when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
+ when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
+ when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(true);
+
+ mUserTracker.set(Collections.singletonList(CURRENT_USER_INFO), 0);
+ }
+
+ /**
+ * Ensures expansion does not happen for full vertical swipes when touch is not available.
+ */
+ @Test
+ public void testFullSwipe_notInitiatedWhenNotAvailable() {
+ mTouchHandler.onGlanceableTouchAvailable(false);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isFalse();
+ }
+
+ /**
+ * Ensures expansion only happens for full vertical swipes when touch is available.
+ */
+ @Test
+ public void testFullSwipe_initiatedWhenAvailable() {
+ mTouchHandler.onGlanceableTouchAvailable(true);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isTrue();
+ }
+
+ @Test
+ public void testFullSwipe_motionUpResetsTouchState() {
+ mTouchHandler.onGlanceableTouchAvailable(true);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ ArgumentCaptor<InputChannelCompat.InputEventListener> inputListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+ verify(mTouchSession).registerInputListener(inputListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isTrue();
+
+ MotionEvent upEvent = Mockito.mock(MotionEvent.class);
+ when(upEvent.getAction()).thenReturn(MotionEvent.ACTION_UP);
+ inputListenerCaptor.getValue().onInputEvent(upEvent);
+ verify(mCommunalViewModel).onResetTouchState();
+ }
+
+ @Test
+ public void testFullSwipe_motionCancelResetsTouchState() {
+ mTouchHandler.onGlanceableTouchAvailable(true);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ ArgumentCaptor<InputChannelCompat.InputEventListener> inputListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+ verify(mTouchSession).registerInputListener(inputListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isTrue();
+
+ MotionEvent upEvent = Mockito.mock(MotionEvent.class);
+ when(upEvent.getAction()).thenReturn(MotionEvent.ACTION_CANCEL);
+ inputListenerCaptor.getValue().onInputEvent(upEvent);
+ verify(mCommunalViewModel).onResetTouchState();
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index 7ebc224..0e98b84 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -50,6 +50,8 @@
import com.android.systemui.ambient.touch.scrim.ScrimController;
import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.FakeUserTracker;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -72,7 +74,9 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
+ private KosmosJavaAdapter mKosmos;
@Mock
CentralSurfaces mCentralSurfaces;
@@ -120,6 +124,9 @@
@Mock
Region mRegion;
+ @Mock
+ CommunalViewModel mCommunalViewModel;
+
@Captor
ArgumentCaptor<Rect> mRectCaptor;
@@ -139,9 +146,11 @@
@Before
public void setup() {
+ mKosmos = new KosmosJavaAdapter(this);
MockitoAnnotations.initMocks(this);
mUserTracker = new FakeUserTracker();
mTouchHandler = new BouncerSwipeTouchHandler(
+ mKosmos.getTestScope(),
mScrimManager,
Optional.of(mCentralSurfaces),
mNotificationShadeWindowController,
@@ -149,6 +158,7 @@
mVelocityTrackerFactory,
mLockPatternUtils,
mUserTracker,
+ mCommunalViewModel,
mFlingAnimationUtils,
mFlingAnimationUtilsClosing,
TOUCH_REGION,
@@ -201,7 +211,6 @@
2)).isTrue();
}
-
/**
* Ensures expansion only happens when touch down happens in valid part of the screen.
*/
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
index 7fd9ce2..204d4b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -26,8 +26,10 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchHandler.TouchSession
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shared.system.InputChannelCompat
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -50,11 +52,11 @@
@RunWith(AndroidJUnit4::class)
class ShadeTouchHandlerTest : SysuiTestCase() {
private var kosmos = testKosmos()
-
private var mCentralSurfaces = mock<CentralSurfaces>()
private var mShadeViewController = mock<ShadeViewController>()
private var mDreamManager = mock<DreamManager>()
private var mTouchSession = mock<TouchSession>()
+ private var communalViewModel = mock<CommunalViewModel>()
private lateinit var mTouchHandler: ShadeTouchHandler
@@ -65,9 +67,11 @@
fun setup() {
mTouchHandler =
ShadeTouchHandler(
+ kosmos.testScope,
Optional.of(mCentralSurfaces),
mShadeViewController,
mDreamManager,
+ communalViewModel,
kosmos.communalSettingsInteractor,
TOUCH_HEIGHT
)
@@ -75,6 +79,7 @@
// Verifies that a swipe down in the gesture region is captured by the shade touch handler.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeDown_captured() {
val captured = swipe(Direction.DOWN)
Truth.assertThat(captured).isTrue()
@@ -82,6 +87,7 @@
// Verifies that a swipe in the upward direction is not captured.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeUp_notCaptured() {
val captured = swipe(Direction.UP)
@@ -91,6 +97,7 @@
// Verifies that a swipe down forwards captured touches to central surfaces for handling.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
fun testSwipeDown_communalEnabled_sentToCentralSurfaces() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -103,7 +110,7 @@
// Verifies that a swipe down forwards captured touches to the shade view for handling.
@Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeDown_communalDisabled_sentToShadeView() {
swipe(Direction.DOWN)
@@ -114,6 +121,7 @@
// Verifies that a swipe down while dreaming forwards captured touches to the shade view for
// handling.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeDown_dreaming_sentToShadeView() {
whenever(mDreamManager.isDreaming).thenReturn(true)
swipe(Direction.DOWN)
@@ -124,6 +132,7 @@
// Verifies that a swipe up is not forwarded to central surfaces.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
fun testSwipeUp_communalEnabled_touchesNotSent() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -137,7 +146,7 @@
// Verifies that a swipe up is not forwarded to the shade view.
@Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeUp_communalDisabled_touchesNotSent() {
swipe(Direction.UP)
@@ -147,6 +156,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testCancelMotionEvent_popsTouchSession() {
swipe(Direction.DOWN)
val event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0)
@@ -154,6 +164,60 @@
verify(mTouchSession).pop()
}
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_initiatedWhenAvailable() {
+ // Indicate touches are available
+ mTouchHandler.onGlanceableTouchAvailable(true)
+
+ // Verify swipe is handled
+ val captured = swipe(Direction.DOWN)
+ Truth.assertThat(captured).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_notInitiatedWhenNotAvailable() {
+ // Indicate touches aren't available
+ mTouchHandler.onGlanceableTouchAvailable(false)
+
+ // Verify swipe is not handled
+ val captured = swipe(Direction.DOWN)
+ Truth.assertThat(captured).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_resetsTouchStateOnUp() {
+ // Indicate touches are available
+ mTouchHandler.onGlanceableTouchAvailable(true)
+
+ // Verify swipe is handled
+ swipe(Direction.DOWN)
+
+ val upEvent: MotionEvent = mock()
+ whenever(upEvent.action).thenReturn(MotionEvent.ACTION_UP)
+ mInputListenerCaptor.lastValue.onInputEvent(upEvent)
+
+ verify(communalViewModel).onResetTouchState()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_resetsTouchStateOnCancel() {
+ // Indicate touches are available
+ mTouchHandler.onGlanceableTouchAvailable(true)
+
+ // Verify swipe is handled
+ swipe(Direction.DOWN)
+
+ val upEvent: MotionEvent = mock()
+ whenever(upEvent.action).thenReturn(MotionEvent.ACTION_CANCEL)
+ mInputListenerCaptor.lastValue.onInputEvent(upEvent)
+
+ verify(communalViewModel).onResetTouchState()
+ }
+
/**
* Simulates a swipe in the given direction and returns true if the touch was intercepted by the
* touch handler's gesture listener.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index c28cf34..a09189e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -372,6 +372,7 @@
nonAuxiliarySubtypes: Int,
): InputMethodModel {
return InputMethodModel(
+ userId = UUID.randomUUID().mostSignificantBits.toInt(),
imeId = UUID.randomUUID().toString(),
subtypes =
List(auxiliarySubtypes + nonAuxiliarySubtypes) {
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/log/CommunalLoggerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
index def8698..28ad269 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
@@ -22,10 +22,13 @@
import com.android.compose.animation.scene.SceneKey
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -53,18 +56,21 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private lateinit var communalInteractor: CommunalInteractor
+ private lateinit var communalSceneInteractor: CommunalSceneInteractor
+ private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var underTest: CommunalLoggerStartable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- communalInteractor = kosmos.communalInteractor
+ communalSceneInteractor = kosmos.communalSceneInteractor
+ keyguardRepository = kosmos.fakeKeyguardRepository
underTest =
CommunalLoggerStartable(
testScope.backgroundScope,
- communalInteractor,
+ communalSceneInteractor,
+ kosmos.keyguardInteractor,
uiEventLogger,
)
underTest.start()
@@ -73,10 +79,13 @@
@Test
fun transitionStateLogging_enterCommunalHub() =
testScope.runTest {
+ // Not dreaming
+ keyguardRepository.setDreamingWithOverlay(false)
+
// Transition state is default (non-communal)
val transitionState =
MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default))
- communalInteractor.setTransitionState(transitionState)
+ communalSceneInteractor.setTransitionState(transitionState)
runCurrent()
// Verify nothing is logged from the default state
@@ -99,12 +108,15 @@
}
@Test
- fun transitionStateLogging_enterCommunalHub_canceled() =
+ fun transitionStateLogging_cancelEnteringCommunalHub() =
testScope.runTest {
+ // Not dreaming
+ keyguardRepository.setDreamingWithOverlay(false)
+
// Transition state is default (non-communal)
val transitionState =
MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default))
- communalInteractor.setTransitionState(transitionState)
+ communalSceneInteractor.setTransitionState(transitionState)
runCurrent()
// Verify nothing is logged from the default state
@@ -132,10 +144,13 @@
@Test
fun transitionStateLogging_exitCommunalHub() =
testScope.runTest {
+ // Not dreaming
+ keyguardRepository.setDreamingWithOverlay(false)
+
// Transition state is communal
val transitionState =
MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal))
- communalInteractor.setTransitionState(transitionState)
+ communalSceneInteractor.setTransitionState(transitionState)
runCurrent()
// Verify SHOWN is logged when it's the default state
@@ -158,12 +173,15 @@
}
@Test
- fun transitionStateLogging_exitCommunalHub_canceled() =
+ fun transitionStateLogging_cancelExitingCommunalHub() =
testScope.runTest {
+ // Not dreaming
+ keyguardRepository.setDreamingWithOverlay(false)
+
// Transition state is communal
val transitionState =
MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal))
- communalInteractor.setTransitionState(transitionState)
+ communalSceneInteractor.setTransitionState(transitionState)
runCurrent()
// Clear the initial SHOWN event from the logger
@@ -188,6 +206,136 @@
verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
}
+ @Test
+ fun transitionStateLogging_dreaming_enterCommunalHub() =
+ testScope.runTest {
+ // Dreaming
+ keyguardRepository.setDreamingWithOverlay(true)
+
+ // Transition state is default (non-communal)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default))
+ communalSceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // Verify nothing is logged from the default state
+ verify(uiEventLogger, never()).log(any())
+
+ // Start transition to communal
+ transitionState.value = transition(to = CommunalScenes.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_START)
+
+ // Finish transition to communal
+ transitionState.value = idle(CommunalScenes.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_FINISH)
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+ }
+
+ @Test
+ fun transitionStateLogging_dreaming_cancelEnteringCommunalHub() =
+ testScope.runTest {
+ // Dreaming
+ keyguardRepository.setDreamingWithOverlay(true)
+
+ // Transition state is default (non-communal)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default))
+ communalSceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // Verify nothing is logged from the default state
+ verify(uiEventLogger, never()).log(any())
+
+ // Start transition to communal
+ transitionState.value = transition(to = CommunalScenes.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_START)
+
+ // Cancel the transition
+ transitionState.value = idle(CommunalScenes.Default)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_CANCEL)
+
+ // Verify neither SHOWN nor GONE is logged
+ verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+ verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+ }
+
+ @Test
+ fun transitionStateLogging_dreaming_exitCommunalHub() =
+ testScope.runTest {
+ // Dreaming
+ keyguardRepository.setDreamingWithOverlay(true)
+
+ // Transition state is communal
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal))
+ communalSceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // Verify SHOWN is logged when it's the default state
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+
+ // Start transition from communal
+ transitionState.value = transition(from = CommunalScenes.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_START)
+
+ // Finish transition to communal
+ transitionState.value = idle(CommunalScenes.Default)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_FINISH)
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+ }
+
+ @Test
+ fun transitionStateLogging_dreaming_cancelExitingCommunalHub() =
+ testScope.runTest {
+ // Dreaming
+ keyguardRepository.setDreamingWithOverlay(true)
+
+ // Transition state is communal
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal))
+ communalSceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // Clear the initial SHOWN event from the logger
+ clearInvocations(uiEventLogger)
+
+ // Start transition from communal
+ transitionState.value = transition(from = CommunalScenes.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_START)
+
+ // Cancel the transition
+ transitionState.value = idle(CommunalScenes.Communal)
+ runCurrent()
+
+ // Verify UiEvent logged
+ verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_CANCEL)
+
+ // Verify neither SHOWN nor GONE is logged
+ verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+ verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+ }
+
private fun transition(
from: SceneKey = CommunalScenes.Default,
to: SceneKey = CommunalScenes.Default,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
index 537ca03..82918a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
@@ -94,6 +94,30 @@
}
@Test
+ fun logTapWidget_componentNotLoggable_doNotLog() {
+ underTest.logTapWidget(
+ componentName = "com.yellow.package/my_test_widget",
+ rank = 2,
+ )
+ verify(statsLogProxy, never())
+ .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt())
+ }
+
+ @Test
+ fun logTapWidget_componentLoggable_logRemoveEvent() {
+ underTest.logTapWidget(
+ componentName = "com.red.package/my_test_widget",
+ rank = 2,
+ )
+ verify(statsLogProxy)
+ .writeCommunalHubWidgetEventReported(
+ SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__TAP,
+ "com.red.package/my_test_widget",
+ 2,
+ )
+ }
+
+ @Test
fun logWidgetsSnapshot_logOnlyLoggableComponents() {
val statsEvents = mutableListOf<StatsEvent>()
underTest.logWidgetsSnapshot(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index d862a21..7a41bc6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.view.viewmodel
+import android.content.ComponentName
import android.content.pm.UserInfo
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
@@ -41,6 +42,7 @@
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
@@ -107,6 +109,7 @@
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
+ @Mock private lateinit var metricsLogger: CommunalMetricsLogger
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -170,7 +173,8 @@
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
mediaHost,
- logcatLogBuffer("CommunalViewModelTest")
+ logcatLogBuffer("CommunalViewModelTest"),
+ metricsLogger,
)
}
@@ -746,6 +750,23 @@
verify(communalInteractor).setScrollPosition(eq(index), eq(offset))
}
+ @Test
+ fun onTapWidget_logEvent() {
+ underTest.onTapWidget(ComponentName("test_pkg", "test_cls"), priority = 10)
+ verify(metricsLogger).logTapWidget("test_pkg/test_cls", rank = 10)
+ }
+
+ @Test
+ fun glanceableTouchAvailable_availableWhenNestedScrollingWithoutConsumption() =
+ testScope.runTest {
+ val touchAvailable by collectLastValue(underTest.glanceableTouchAvailable)
+ assertThat(touchAvailable).isTrue()
+ underTest.onHubTouchConsumed()
+ assertThat(touchAvailable).isFalse()
+ underTest.onNestedScrolling()
+ assertThat(touchAvailable).isTrue()
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO
with(userRepository) {
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/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 60c9bb0..4c24ce2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -54,6 +54,7 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.complication.ComplicationHostViewController
import com.android.systemui.complication.ComplicationLayoutEngine
@@ -660,6 +661,7 @@
verify(mDreamOverlayCallback).onRedirectWake(true)
client.onWakeRequested()
verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), isNull())
+ verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 18839e6..59802ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -23,7 +23,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
-import com.android.systemui.classifier.falsingManager
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.qsTileFactory
@@ -73,7 +72,6 @@
QSLongPressEffect(
vibratorHelper,
kosmos.keyguardStateController,
- kosmos.falsingManager,
)
longPressEffect.callback = callback
longPressEffect.qsTile = qsTile
@@ -304,13 +302,12 @@
}
@Test
- fun getStateForClick_withFalseTapWhenLocked_returnsIdle() {
+ fun getStateForClick_whenKeyguardsIsShowing_returnsIdle() {
// GIVEN an active tile
qsTile.state?.state = Tile.STATE_ACTIVE
- // GIVEN that the device is locked and a false tap is detected
- whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
- kosmos.falsingManager.setFalseTap(true)
+ // GIVEN that the keyguard is showing
+ whenever(kosmos.keyguardStateController.isShowing).thenReturn(true)
// WHEN determining the state of a click action
val clickState = longPressEffect.getStateForClick()
@@ -324,9 +321,8 @@
// GIVEN an active tile
qsTile.state?.state = Tile.STATE_ACTIVE
- // GIVEN that the device is locked and a false tap is not detected
- whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
- kosmos.falsingManager.setFalseTap(false)
+ // GIVEN that the keyguard is not showing
+ whenever(kosmos.keyguardStateController.isShowing).thenReturn(false)
// WHEN determining the state of a click action
val clickState = longPressEffect.getStateForClick()
@@ -340,9 +336,8 @@
// GIVEN that the tile is null
longPressEffect.qsTile = null
- // GIVEN that the device is locked and a false tap is not detected
- whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
- kosmos.falsingManager.setFalseTap(false)
+ // GIVEN that the keyguard is not showing
+ whenever(kosmos.keyguardStateController.isShowing).thenReturn(false)
// WHEN determining the state of a click action
val clickState = longPressEffect.getStateForClick()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
index 857cdce..274880b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
@@ -56,9 +56,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
- .thenReturn(listOf())
-
underTest =
InputMethodRepositoryImpl(
backgroundDispatcher = kosmos.testDispatcher,
@@ -71,10 +68,16 @@
testScope.runTest {
whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
.thenReturn(listOf())
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
+ whenever(
+ inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+ any(),
+ anyBoolean(),
+ eq(USER_HANDLE)
+ )
+ )
.thenReturn(listOf())
- assertThat(underTest.enabledInputMethods(USER_ID, fetchSubtypes = true).count())
+ assertThat(underTest.enabledInputMethods(USER_HANDLE, fetchSubtypes = true).count())
.isEqualTo(0)
}
@@ -83,11 +86,20 @@
testScope.runTest {
val subtypeId = 123
val isAuxiliary = true
+ val selectedImiId = "imiId"
+ val selectedImi = mock<InputMethodInfo>()
+ whenever(selectedImi.id).thenReturn(selectedImiId)
+ whenever(inputMethodManager.getCurrentInputMethodInfoAsUser(eq(USER_HANDLE)))
+ .thenReturn(selectedImi)
whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
- .thenReturn(listOf(mock<InputMethodInfo>()))
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
- .thenReturn(listOf())
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
+ .thenReturn(listOf(selectedImi))
+ whenever(
+ inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+ eq(selectedImiId),
+ anyBoolean(),
+ eq(USER_HANDLE)
+ )
+ )
.thenReturn(
listOf(
InputMethodSubtype.InputMethodSubtypeBuilder()
@@ -97,7 +109,7 @@
)
)
- val result = underTest.selectedInputMethodSubtypes()
+ val result = underTest.selectedInputMethodSubtypes(USER_HANDLE)
assertThat(result).hasSize(1)
assertThat(result.first().subtypeId).isEqualTo(subtypeId)
assertThat(result.first().isAuxiliary).isEqualTo(isAuxiliary)
@@ -108,7 +120,7 @@
testScope.runTest {
val displayId = 7
- underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes = */ true)
+ underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes= */ true)
verify(inputMethodManager)
.showInputMethodPickerFromSystem(
@@ -118,7 +130,6 @@
}
companion object {
- private const val USER_ID = 100
- private val USER_HANDLE = UserHandle.of(USER_ID)
+ private val USER_HANDLE = UserHandle.of(100)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
index d23ff2a..8e6de2f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
@@ -143,6 +143,7 @@
nonAuxiliarySubtypes: Int,
): InputMethodModel {
return InputMethodModel(
+ userId = UUID.randomUUID().mostSignificantBits.toInt(),
imeId = UUID.randomUUID().toString(),
subtypes =
List(auxiliarySubtypes + nonAuxiliarySubtypes) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index b885800..783e3b5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -33,16 +33,21 @@
package com.android.systemui.keyguard.domain.interactor
import android.os.PowerManager
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.service.dream.dreamManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -51,6 +56,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -69,15 +75,22 @@
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class FromDozingTransitionInteractorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+@EnableFlags(FLAG_COMMUNAL_HUB)
+class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ this.fakeCommunalSceneRepository =
+ spy(FakeCommunalSceneRepository(applicationScope = applicationCoroutineScope))
}
private val testScope = kosmos.testScope
@@ -86,6 +99,18 @@
private lateinit var powerInteractor: PowerInteractor
private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setup() {
powerInteractor = kosmos.powerInteractor
@@ -108,7 +133,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToLockscreen_onWakeup() =
testScope.runTest {
powerInteractor.setAwakeForTest()
@@ -123,7 +148,8 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, Flags.FLAG_COMMUNAL_HUB)
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun testTransitionToLockscreen_onPowerButtonPress_canDream_glanceableHubAvailable() =
testScope.runTest {
whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
@@ -143,7 +169,25 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ fun testTransitionToLockscreen_onPowerButtonPress_canDream_ktfRefactor() =
+ testScope.runTest {
+ whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+ clearInvocations(kosmos.fakeCommunalSceneRepository)
+
+ powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+ runCurrent()
+
+ // If dreaming is possible and communal is available, then we should transition to
+ // GLANCEABLE_HUB when waking up due to power button press.
+ verify(kosmos.fakeCommunalSceneRepository)
+ .changeScene(CommunalScenes.Communal, CommunalTransitionKeys.Immediately)
+ }
+
+ @Test
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToLockscreen_onPowerButtonPress_canNotDream_glanceableHubAvailable() =
testScope.runTest {
whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(false)
@@ -163,7 +207,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToLockscreen_onPowerButtonPress_canNDream_glanceableHubNotAvailable() =
testScope.runTest {
whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
@@ -183,7 +227,8 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() =
testScope.runTest {
kosmos.fakeCommunalSceneRepository.setTransitionState(
@@ -203,7 +248,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() =
testScope.runTest {
kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
@@ -219,7 +264,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop_evenIfIdleOnCommunal() =
testScope.runTest {
kosmos.fakeCommunalSceneRepository.setTransitionState(
@@ -240,7 +285,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
@Suppress("ktlint:standard:max-line-length")
fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() =
testScope.runTest {
@@ -254,7 +299,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() =
testScope.runTest {
kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
@@ -268,7 +313,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() =
testScope.runTest {
powerInteractor.setAwakeForTest()
@@ -306,7 +351,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
@Suppress("ktlint:standard:max-line-length")
fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() =
testScope.runTest {
@@ -342,7 +387,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() =
testScope.runTest {
powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
index 65aa825..7424320 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
@@ -32,11 +32,13 @@
package com.android.systemui.keyguard.domain.interactor
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -49,13 +51,13 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.testKosmos
-import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
@@ -108,6 +110,7 @@
@Test
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun testShowWhenLockedActivity_noLongerOnTop_transitionsToGlanceableHub_ifIdleOnCommunal() =
testScope.runTest {
kosmos.fakeCommunalSceneRepository.setTransitionState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 2d77f4f..75c0d3b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -22,6 +22,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
@@ -86,7 +87,8 @@
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var shadeInteractor: ShadeInteractor
- @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
+ @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
private val kosmos = testKosmos()
@@ -194,6 +196,7 @@
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
logger = logger,
+ metricsLogger = metricsLogger,
devicePolicyManager = devicePolicyManager,
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
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/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt
index aba21c9..cd0a11c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.res.Configuration
+import android.util.LayoutDirection
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -33,6 +35,8 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -73,6 +77,9 @@
R.dimen.dreaming_to_hub_transition_dream_overlay_translation_x,
-100
)
+ val configuration: Configuration = mock()
+ whenever(configuration.layoutDirection).thenReturn(LayoutDirection.LTR)
+ configurationRepository.onConfigurationChange(configuration)
val values by collectValues(underTest.dreamOverlayTranslationX)
assertThat(values).isEmpty()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModelTest.kt
index 11890c7..69361ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModelTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.res.Configuration
+import android.util.LayoutDirection
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -33,6 +35,8 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -69,6 +73,9 @@
@Test
fun dreamOverlayTranslationX() =
testScope.runTest {
+ val config: Configuration = mock()
+ whenever(config.layoutDirection).thenReturn(LayoutDirection.LTR)
+ configurationRepository.onConfigurationChange(config)
configurationRepository.setDimensionPixelSize(
R.dimen.hub_to_dreaming_transition_dream_overlay_translation_x,
100
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
index 1aa1ec4..d2be649 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.res.Configuration
+import android.util.LayoutDirection
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -34,6 +36,8 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -77,6 +81,10 @@
@Test
fun lockscreenTranslationX() =
testScope.runTest {
+ val config: Configuration = mock()
+ whenever(config.layoutDirection).thenReturn(LayoutDirection.LTR)
+ configurationRepository.onConfigurationChange(config)
+
configurationRepository.setDimensionPixelSize(
R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x,
100
@@ -102,6 +110,10 @@
@Test
fun lockscreenTranslationX_resetsAfterCancellation() =
testScope.runTest {
+ val config: Configuration = mock()
+ whenever(config.layoutDirection).thenReturn(LayoutDirection.LTR)
+ configurationRepository.onConfigurationChange(config)
+
configurationRepository.setDimensionPixelSize(
R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x,
100
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 3db9ef1..bca83f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -257,7 +257,6 @@
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
return LockscreenSceneViewModel(
- applicationScope = testScope.backgroundScope,
deviceEntryInteractor = kosmos.deviceEntryInteractor,
communalInteractor = kosmos.communalInteractor,
touchHandling =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt
index 68a7b7e..a60a486 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.res.Configuration
+import android.util.LayoutDirection
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -34,6 +36,9 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -82,6 +87,9 @@
R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x,
-100
)
+ val configuration = mock<Configuration>()
+ whenever(configuration.layoutDirection).thenReturn(LayoutDirection.LTR)
+ configurationRepository.onConfigurationChange(configuration)
val values by collectValues(underTest.keyguardTranslationX)
assertThat(values).isEmpty()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
index 56b3679..42db96e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
@@ -23,8 +23,8 @@
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
+import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
-import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -42,6 +42,8 @@
@RunWith(AndroidJUnit4::class)
class GridConsistencyInteractorTest : SysuiTestCase() {
+ data object NoopGridLayoutType : GridLayoutType
+
private val kosmos =
testKosmos().apply {
defaultLargeTilesRepository =
@@ -54,6 +56,11 @@
TileSpec.create("largeD"),
)
}
+ gridConsistencyInteractorsMap =
+ mapOf(
+ Pair(NoopGridLayoutType, noopGridConsistencyInteractor),
+ Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)
+ )
}
private val underTest = with(kosmos) { gridConsistencyInteractor }
@@ -71,7 +78,7 @@
with(kosmos) {
testScope.runTest {
// Using the no-op grid consistency interactor
- gridLayoutTypeRepository.setLayout(PartitionedGridLayoutType)
+ gridLayoutTypeRepository.setLayout(NoopGridLayoutType)
// Setting an invalid layout with holes
// [ Large A ] [ sa ]
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt
deleted file mode 100644
index 55b7454..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.ui.compose
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
-import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
-import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class PartitionedGridLayoutTest : SysuiTestCase() {
- private val kosmos =
- testKosmos().apply {
- defaultLargeTilesRepository =
- object : DefaultLargeTilesRepository {
- override val defaultLargeTiles: Set<TileSpec> = setOf(TileSpec.create("large"))
- }
- }
-
- private val underTest = with(kosmos) { PartitionedGridLayout(partitionedGridViewModel) }
-
- @Test
- fun correctPagination_underOnePage_partitioned_sameRelativeOrder() =
- with(kosmos) {
- testScope.runTest {
- val rows = 3
- val columns = 4
-
- val tiles =
- listOf(
- largeTile(),
- smallTile(),
- smallTile(),
- largeTile(),
- largeTile(),
- smallTile()
- )
- val (smallTiles, largeTiles) =
- tiles.partition { iconTilesViewModel.isIconTile(it.spec) }
-
- // [L L] [L L]
- // [L L]
- // [S] [S] [S]
-
- val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns)
-
- Truth.assertThat(pages).hasSize(1)
- Truth.assertThat(pages[0]).isEqualTo(largeTiles + smallTiles)
- }
- }
-
- @Test
- fun correctPagination_twoPages_partitioned_sameRelativeOrder() =
- with(kosmos) {
- testScope.runTest {
- val rows = 3
- val columns = 4
-
- val tiles =
- listOf(
- largeTile(),
- smallTile(),
- smallTile(),
- largeTile(),
- smallTile(),
- smallTile(),
- largeTile(),
- smallTile(),
- smallTile(),
- )
- // --- Page 1 ---
- // [L L] [L L]
- // [L L]
- // [S] [S] [S] [S]
- // --- Page 2 ---
- // [S] [S]
-
- val (smallTiles, largeTiles) =
- tiles.partition { iconTilesViewModel.isIconTile(it.spec) }
-
- val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns)
-
- val expectedPage0 = largeTiles + smallTiles.take(4)
- val expectedPage1 = smallTiles.drop(4)
-
- Truth.assertThat(pages).hasSize(2)
- Truth.assertThat(pages[0]).isEqualTo(expectedPage0)
- Truth.assertThat(pages[1]).isEqualTo(expectedPage1)
- }
- }
-
- companion object {
- fun largeTile() = MockTileViewModel(TileSpec.create("large"))
-
- fun smallTile() = MockTileViewModel(TileSpec.create("small"))
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index d5c9102..a67e7c6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -21,64 +21,68 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.animation.Expandable
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
-import com.google.common.truth.Truth
-import kotlin.coroutines.EmptyCoroutineContext
+import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(android.app.Flags.FLAG_MODES_UI)
class ModesTileUserActionInteractorTest : SysuiTestCase() {
- private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val kosmos = testKosmos()
+ private val inputHandler = kosmos.qsTileIntentUserInputHandler
+ private val mockDialogDelegate = kosmos.mockModesDialogDelegate
- @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
- @Mock private lateinit var dialogDelegate: ModesDialogDelegate
- @Mock private lateinit var mockDialog: SystemUIDialog
+ private val underTest =
+ ModesTileUserActionInteractor(
+ inputHandler,
+ mockDialogDelegate,
+ )
- private lateinit var underTest: ModesTileUserActionInteractor
+ @Test
+ fun handleClick_active() = runTest {
+ val expandable = mock<Expandable>()
+ underTest.handleInput(
+ QSTileInputTestKtx.click(data = ModesTileModel(true), expandable = expandable))
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
-
- whenever(dialogDelegate.createDialog()).thenReturn(mockDialog)
-
- underTest =
- ModesTileUserActionInteractor(
- EmptyCoroutineContext,
- inputHandler,
- dialogTransitionAnimator,
- dialogDelegate,
- )
+ verify(mockDialogDelegate).showDialog(eq(expandable))
}
@Test
- fun handleClick() = runTest {
- underTest.handleInput(QSTileInputTestKtx.click(ModesTileModel(false)))
+ fun handleClick_inactive() = runTest {
+ val expandable = mock<Expandable>()
+ underTest.handleInput(
+ QSTileInputTestKtx.click(data = ModesTileModel(false), expandable = expandable))
- verify(mockDialog).show()
+ verify(mockDialogDelegate).showDialog(eq(expandable))
}
@Test
- fun handleLongClick() = runTest {
+ fun handleLongClick_active() = runTest {
+ underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(true)))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
+ }
+ }
+
+ @Test
+ fun handleLongClick_inactive() = runTest {
underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(false)))
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
- Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 9249621..3ca802e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -93,7 +93,6 @@
sceneContainerStartable.start()
underTest =
QuickSettingsSceneViewModel(
- applicationScope = testScope.backgroundScope,
brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 39b3662..228d61a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -143,7 +143,6 @@
private val lockscreenSceneViewModel by lazy {
LockscreenSceneViewModel(
- applicationScope = testScope.backgroundScope,
deviceEntryInteractor = deviceEntryInteractor,
communalInteractor = communalInteractor,
touchHandling =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
index 0ab6a82..a4992e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
@@ -53,7 +53,6 @@
fun setUp() {
underTest =
GoneSceneViewModel(
- applicationScope = testScope.backgroundScope,
shadeInteractor = kosmos.shadeInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index da22c6d..343b6bd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -25,6 +25,7 @@
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activatable.activateIn
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
@@ -59,6 +60,7 @@
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -78,6 +80,11 @@
private val underTest: ShadeSceneViewModel by lazy { kosmos.shadeSceneViewModel }
+ @Before
+ fun setUp() {
+ underTest.activateIn(testScope)
+ }
+
@Test
fun upTransitionSceneKey_deviceLocked_lockScreen() =
testScope.runTest {
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/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
index 8810ade..7b87aeb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
@@ -20,7 +20,6 @@
import android.app.Notification
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_LOW
-import android.os.UserHandle
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -475,20 +474,6 @@
val collectionListener: NotifCollectionListener =
argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue
-
- var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
- get() =
- fakeSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- UserHandle.USER_CURRENT,
- ) == 1
- set(value) {
- fakeSettings.putIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- if (value) 1 else 2,
- UserHandle.USER_CURRENT,
- )
- }
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
index 7e9f437..3fd9c21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -18,21 +18,23 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.Notification
-import android.os.UserHandle
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.dumpManager
import com.android.systemui.flags.andSceneContainer
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.setTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -43,12 +45,15 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.lockScreenShowOnlyUnseenNotificationsSetting
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.testKosmos
import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
@@ -73,13 +78,23 @@
@RunWith(ParameterizedAndroidJunit4::class)
class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() {
- private val kosmos = Kosmos()
+ private val kosmos =
+ testKosmos().apply {
+ testDispatcher = UnconfinedTestDispatcher()
+ statusBarStateController = mock()
+ fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+ }
- private val headsUpManager: HeadsUpManager = mock()
- private val keyguardRepository = FakeKeyguardRepository()
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val keyguardRepository
+ get() = kosmos.fakeKeyguardRepository
+
+ private val keyguardTransitionRepository
+ get() = kosmos.fakeKeyguardTransitionRepository
+
+ private val statusBarStateController
+ get() = kosmos.statusBarStateController
+
private val notifPipeline: NotifPipeline = mock()
- private val statusBarStateController: StatusBarStateController = mock()
init {
mSetFlagsRule.setFlagsParameterization(flags)
@@ -253,7 +268,7 @@
collectionListener.onEntryAdded(fakeEntry)
// GIVEN: The setting for filtering unseen notifications is disabled
- showOnlyUnseenNotifsOnKeyguardSetting = false
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = false
// GIVEN: The pipeline has registered the unseen filter for invalidation
val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock()
@@ -267,7 +282,7 @@
assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
// WHEN: The secure setting is changed
- showOnlyUnseenNotifsOnKeyguardSetting = true
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = true
// THEN: The pipeline is invalidated
verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), any())
@@ -608,35 +623,25 @@
private fun runKeyguardCoordinatorTest(
testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
) {
- val testDispatcher = UnconfinedTestDispatcher()
- val testScope = TestScope(testDispatcher)
- val fakeSettings =
- FakeSettings().apply {
- putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
- }
- val seenNotificationsInteractor =
- SeenNotificationsInteractor(ActiveNotificationListRepository())
val keyguardCoordinator =
OriginalUnseenKeyguardCoordinator(
- testDispatcher,
- mock<DumpManager>(),
- headsUpManager,
- keyguardRepository,
- kosmos.keyguardTransitionInteractor,
- KeyguardCoordinatorLogger(logcatLogBuffer()),
- testScope.backgroundScope,
- fakeSettings,
- seenNotificationsInteractor,
- statusBarStateController,
+ dumpManager = kosmos.dumpManager,
+ headsUpManager = kosmos.headsUpManager,
+ keyguardRepository = kosmos.keyguardRepository,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ logger = KeyguardCoordinatorLogger(logcatLogBuffer()),
+ scope = kosmos.testScope.backgroundScope,
+ seenNotificationsInteractor = kosmos.seenNotificationsInteractor,
+ statusBarStateController = kosmos.statusBarStateController,
sceneInteractor = kosmos.sceneInteractor,
)
keyguardCoordinator.attach(notifPipeline)
- testScope.runTest {
+ kosmos.testScope.runTest {
KeyguardCoordinatorTestScope(
keyguardCoordinator,
- testScope,
- seenNotificationsInteractor,
- fakeSettings,
+ kosmos.testScope,
+ kosmos.seenNotificationsInteractor,
+ kosmos.fakeSettings,
)
.testBlock()
}
@@ -658,21 +663,8 @@
argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue
val onHeadsUpChangedListener: OnHeadsUpChangedListener
- get() = argumentCaptor { verify(headsUpManager).addListener(capture()) }.lastValue
-
- var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
get() =
- fakeSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- UserHandle.USER_CURRENT,
- ) == 1
- set(value) {
- fakeSettings.putIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- if (value) 1 else 2,
- UserHandle.USER_CURRENT,
- )
- }
+ argumentCaptor { verify(kosmos.headsUpManager).addListener(capture()) }.lastValue
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
new file mode 100644
index 0000000..2159b86
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class SeenNotificationsInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val underTest
+ get() = kosmos.seenNotificationsInteractor
+
+ @Test
+ fun testNoFilteredOutSeenNotifications() = runTest {
+ val hasFilteredOutSeenNotifications by
+ collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ underTest.setHasFilteredOutSeenNotifications(false)
+
+ assertThat(hasFilteredOutSeenNotifications).isFalse()
+ }
+
+ @Test
+ fun testHasFilteredOutSeenNotifications() = runTest {
+ val hasFilteredOutSeenNotifications by
+ collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ underTest.setHasFilteredOutSeenNotifications(true)
+
+ assertThat(hasFilteredOutSeenNotifications).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalismPrototype.FLAG_NAME)
+ fun topOngoingAndUnseenNotification() = runTest {
+ val entry1 = NotificationEntryBuilder().setTag("entry1").build()
+ val entry2 = NotificationEntryBuilder().setTag("entry2").build()
+
+ underTest.setTopOngoingNotification(null)
+ underTest.setTopUnseenNotification(null)
+
+ assertThat(underTest.isTopOngoingNotification(entry1)).isFalse()
+ assertThat(underTest.isTopOngoingNotification(entry2)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry1)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry2)).isFalse()
+
+ underTest.setTopOngoingNotification(entry1)
+ underTest.setTopUnseenNotification(entry2)
+
+ assertThat(underTest.isTopOngoingNotification(entry1)).isTrue()
+ assertThat(underTest.isTopOngoingNotification(entry2)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry1)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry2)).isTrue()
+ }
+
+ fun testShowOnlyUnseenNotifsOnKeyguardSetting() = runTest {
+ val settingEnabled by
+ collectLastValue(underTest.isLockScreenShowOnlyUnseenNotificationsEnabled())
+
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = false
+ testScheduler.runCurrent()
+ assertThat(settingEnabled).isFalse()
+
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = true
+ testScheduler.runCurrent()
+ assertThat(settingEnabled).isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 8f9da3b..9a862fc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -60,6 +60,7 @@
// For creating TestableHeadsUpManager
@Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
private val mUiEventLoggerFake = UiEventLoggerFake()
+ @Mock private lateinit var mHeadsUpManagerLogger: HeadsUpManagerLogger
@Mock private lateinit var mBgHandler: Handler
@@ -82,7 +83,8 @@
// Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
// declaration, where mocks are null
- mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake, mBgHandler)
+ mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake,
+ mHeadsUpManagerLogger, mBgHandler)
testableHeadsUpManager =
TestableHeadsUpManager(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index df07b44..9005ae3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -81,6 +81,7 @@
static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
+
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
@Mock private Handler mBgHandler;
@Mock private DumpManager dumpManager;
@@ -149,7 +150,8 @@
@Override
public void SysuiSetup() throws Exception {
super.SysuiSetup();
- mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mBgHandler);
+ mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mLogger,
+ mBgHandler);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index b91bde4..7a6838a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -179,7 +179,8 @@
mContext
.getOrCreateTestableResources()
.addOverride(R.integer.ambient_notification_extension_time, 500)
- mAvalancheController = AvalancheController(dumpManager, mUiEventLogger, mBgHandler)
+ mAvalancheController = AvalancheController(dumpManager, mUiEventLogger,
+ mHeadsUpManagerLogger, mBgHandler)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index fdfc7f1..62161bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -18,6 +18,8 @@
package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
+import android.content.Intent
+import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.TestModeBuilder
@@ -27,32 +29,46 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
class ModesDialogViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- val repository = kosmos.fakeZenModeRepository
- val interactor = kosmos.zenModeInteractor
+ private val repository = kosmos.fakeZenModeRepository
+ private val interactor = kosmos.zenModeInteractor
+ private val mockDialogDelegate = kosmos.mockModesDialogDelegate
- val underTest = ModesDialogViewModel(context, interactor, kosmos.testDispatcher)
+ private val underTest =
+ ModesDialogViewModel(context, interactor, kosmos.testDispatcher, mockDialogDelegate)
@Test
- fun tiles_filtersOutDisabledModes() =
+ fun tiles_filtersOutUserDisabledModes() =
testScope.runTest {
val tiles by collectLastValue(underTest.tiles)
repository.addModes(
listOf(
- TestModeBuilder().setName("Disabled").setEnabled(false).build(),
+ TestModeBuilder()
+ .setName("Disabled by user")
+ .setEnabled(false, /* byUser= */ true)
+ .build(),
+ TestModeBuilder()
+ .setName("Disabled by other")
+ .setEnabled(false, /* byUser= */ false)
+ .build(),
TestModeBuilder.MANUAL_DND,
TestModeBuilder()
.setName("Enabled")
@@ -61,19 +77,25 @@
.build(),
TestModeBuilder()
.setName("Disabled with manual")
- .setEnabled(false)
+ .setEnabled(false, /* byUser= */ true)
.setManualInvocationAllowed(true)
.build(),
- ))
+ )
+ )
runCurrent()
- assertThat(tiles?.size).isEqualTo(2)
+ assertThat(tiles?.size).isEqualTo(3)
with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Disabled by other")
+ assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.enabled).isEqualTo(false)
+ }
+ with(tiles?.elementAt(1)!!) {
assertThat(this.text).isEqualTo("Manual DND")
assertThat(this.subtext).isEqualTo("On")
assertThat(this.enabled).isEqualTo(true)
}
- with(tiles?.elementAt(1)!!) {
+ with(tiles?.elementAt(2)!!) {
assertThat(this.text).isEqualTo("Enabled")
assertThat(this.subtext).isEqualTo("Off")
assertThat(this.enabled).isEqualTo(false)
@@ -108,7 +130,8 @@
.setActive(false)
.setManualInvocationAllowed(false)
.build(),
- ))
+ )
+ )
runCurrent()
assertThat(tiles?.size).isEqualTo(3)
@@ -130,6 +153,117 @@
}
@Test
+ fun tiles_stableWhileCollecting() =
+ testScope.runTest {
+ val job = Job()
+ val tiles by collectLastValue(underTest.tiles, context = job)
+
+ repository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setName("Active without manual")
+ .setActive(true)
+ .setManualInvocationAllowed(false)
+ .build(),
+ TestModeBuilder()
+ .setName("Active with manual")
+ .setActive(true)
+ .setManualInvocationAllowed(true)
+ .build(),
+ TestModeBuilder()
+ .setName("Inactive with manual")
+ .setActive(false)
+ .setManualInvocationAllowed(true)
+ .build(),
+ TestModeBuilder()
+ .setName("Inactive without manual")
+ .setActive(false)
+ .setManualInvocationAllowed(false)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(3)
+
+ // Check that tile is initially present
+ with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Active without manual")
+ assertThat(this.subtext).isEqualTo("On")
+ assertThat(this.enabled).isEqualTo(true)
+
+ // Click tile to toggle it
+ this.onClick()
+ runCurrent()
+ }
+ // Check that tile is still present at the same location, but turned off
+ assertThat(tiles?.size).isEqualTo(3)
+ with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Active without manual")
+ assertThat(this.subtext).isEqualTo("Manage in settings")
+ assertThat(this.enabled).isEqualTo(false)
+ }
+
+ // Stop collecting, then start again
+ job.cancel()
+ val tiles2 by collectLastValue(underTest.tiles)
+ runCurrent()
+
+ // Check that tile is now gone
+ assertThat(tiles2?.size).isEqualTo(2)
+ assertThat(tiles2?.elementAt(0)!!.text).isEqualTo("Active with manual")
+ assertThat(tiles2?.elementAt(1)!!.text).isEqualTo("Inactive with manual")
+ }
+
+ @Test
+ fun tiles_filtersOutRemovedModes() =
+ testScope.runTest {
+ val job = Job()
+ val tiles by collectLastValue(underTest.tiles, context = job)
+
+ repository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setId("A")
+ .setName("Active without manual")
+ .setActive(true)
+ .setManualInvocationAllowed(false)
+ .build(),
+ TestModeBuilder()
+ .setId("B")
+ .setName("Active with manual")
+ .setActive(true)
+ .setManualInvocationAllowed(true)
+ .build(),
+ TestModeBuilder()
+ .setId("C")
+ .setName("Inactive with manual")
+ .setActive(false)
+ .setManualInvocationAllowed(true)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(3)
+
+ repository.removeMode("A")
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(2)
+
+ repository.removeMode("B")
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(1)
+
+ repository.removeMode("C")
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(0)
+ }
+
+ @Test
fun onClick_togglesTileState() =
testScope.runTest {
val tiles by collectLastValue(underTest.tiles)
@@ -161,4 +295,141 @@
assertThat(tiles?.first()?.enabled).isFalse()
}
+
+ @Test
+ fun onClick_noManualActivation() =
+ testScope.runTest {
+ val job = Job()
+ val tiles by collectLastValue(underTest.tiles, context = job)
+
+ repository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setName("Active without manual")
+ .setActive(true)
+ .setManualInvocationAllowed(false)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(1)
+
+ // Click tile to toggle it off
+ tiles?.elementAt(0)!!.onClick()
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(1)
+ with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Active without manual")
+ assertThat(this.subtext).isEqualTo("Manage in settings")
+ assertThat(this.enabled).isEqualTo(false)
+
+ // Press the tile again
+ this.onClick()
+ runCurrent()
+ }
+
+ // Check that nothing happened
+ with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Active without manual")
+ assertThat(this.subtext).isEqualTo("Manage in settings")
+ assertThat(this.enabled).isEqualTo(false)
+ }
+ }
+
+ @Test
+ fun onClick_setUp() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tiles)
+
+ repository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setId("ID")
+ .setName("Disabled by other")
+ .setEnabled(false, /* byUser= */ false)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(1)
+ with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Disabled by other")
+ assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.enabled).isEqualTo(false)
+
+ // Click the tile
+ this.onClick()
+ runCurrent()
+ }
+
+ // Check that it launched the correct intent
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(mockDialogDelegate).launchFromDialog(intentCaptor.capture())
+ val intent = intentCaptor.lastValue
+ assertThat(intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
+ assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
+ .isEqualTo("ID")
+
+ // Check that nothing happened to the tile
+ with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Disabled by other")
+ assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.enabled).isEqualTo(false)
+ }
+ }
+
+ @Test
+ fun onLongClick_launchesIntent() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tiles)
+ val intentCaptor = argumentCaptor<Intent>()
+
+ val modeId = "id"
+ repository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setId(modeId)
+ .setId("A")
+ .setActive(true)
+ .setManualInvocationAllowed(true)
+ .build(),
+ TestModeBuilder()
+ .setId(modeId)
+ .setId("B")
+ .setActive(false)
+ .setManualInvocationAllowed(true)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(2)
+
+ // Trigger onLongClick for A
+ tiles?.first()?.onLongClick?.let { it() }
+ runCurrent()
+
+ // Check that it launched the correct intent
+ verify(mockDialogDelegate).launchFromDialog(intentCaptor.capture())
+ var intent = intentCaptor.lastValue
+ assertThat(intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
+ assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
+ .isEqualTo("A")
+
+ clearInvocations(mockDialogDelegate)
+
+ // Trigger onLongClick for B
+ tiles?.last()?.onLongClick?.let { it() }
+ runCurrent()
+
+ // Check that it launched the correct intent
+ verify(mockDialogDelegate).launchFromDialog(intentCaptor.capture())
+ intent = intentCaptor.lastValue
+ assertThat(intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
+ assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
+ .isEqualTo("B")
+ }
}
diff --git a/packages/SystemUI/res/color/disconnected_network_primary_color.xml b/packages/SystemUI/res/color/disconnected_network_primary_color.xml
new file mode 100644
index 0000000..536bf78
--- /dev/null
+++ b/packages/SystemUI/res/color/disconnected_network_primary_color.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/materialColorPrimaryContainer" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/app_clips_backlinks_drop_down_entry.xml b/packages/SystemUI/res/layout/app_clips_backlinks_drop_down_entry.xml
new file mode 100644
index 0000000..7eab340
--- /dev/null
+++ b/packages/SystemUI/res/layout/app_clips_backlinks_drop_down_entry.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:drawablePadding="4dp"
+ android:ellipsize="end"
+ android:gravity="center_vertical"
+ android:paddingHorizontal="8dp"
+ android:textColor="?android:textColorSecondary" />
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 22d156d..0029180 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -170,8 +170,7 @@
android:layout_height="28dp"
android:layout_marginStart="7dp"
android:layout_marginEnd="16dp"
- android:layout_gravity="center_vertical"
- android:background="?android:attr/textColorSecondary"/>
+ android:layout_gravity="center_vertical"/>
<FrameLayout
android:layout_width="@dimen/settingslib_switch_track_width"
diff --git a/packages/SystemUI/res/raw/trackpad_back_edu.json b/packages/SystemUI/res/raw/trackpad_back_edu.json
index 793833d..908d26f 100644
--- a/packages/SystemUI/res/raw/trackpad_back_edu.json
+++ b/packages/SystemUI/res/raw/trackpad_back_edu.json
@@ -1 +1 @@
-{"v":"5.12.1","fr":60,"ip":0,"op":900,"w":554,"h":564,"nm":"Trackpad-JSON_BackGesture","ddd":0,"assets":[{"id":"comp_0","nm":"Back_LeftDismiss","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"release Scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":0,"k":79},"y":{"a":0,"k":197}},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.08,0.08,0.08],"y":[0.47,0.47,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.999,0.999,0.999],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":254,"s":[105,105,100]},{"t":266,"s":[50,50,100]}]}},"ao":0,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiary","cl":"onTertiary","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":151,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":255,"s":[100]},{"t":258,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[-0.692,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.308,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[4.009,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[8.291,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[13.138,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[15.452,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[16.757,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[17.542,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[18.002,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[18.238,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[18.308,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[21.331,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.006,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.308,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.382,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.657,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[24.165,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[24.794,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[25.403,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[25.942,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[26.411,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[26.822,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[27.186,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[27.511,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[27.803,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.069,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.311,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.534,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.739,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.928,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.103,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.267,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.419,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.56,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.693,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.816,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.932,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.041,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.142,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.238,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.327,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.411,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.489,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.563,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.632,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.696,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.756,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.812,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.864,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.913,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.958,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.039,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.074,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.107,0,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.137,0,0],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.164,0,0],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.188,0,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.21,0,0],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.23,0,0],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.247,0,0],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.274,0,0],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.305,0,0],"t":215,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.36,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[5.459,5.2],[-3.459,0],[5.459,-5.2]],"c":false}]},{"i":{"x":0.02,"y":1},"o":{"x":0.167,"y":0.167},"t":152,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.779,4.88],[-3.459,0],[4.779,-4.88]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":159,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.12,"y":0},"t":162,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"t":217,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,9.2],[-3.459,0],[3.459,-9.2]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.121568627656,0.211764708161,0.101960785687,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":4},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Vector 1","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":257,"s":[100]},{"t":260,"s":[0]}]},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.06],"y":[0.15]},"t":160,"s":[-14]},{"t":189,"s":[0]}]},"y":{"a":0,"k":0}},"a":{"a":0,"k":[32,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"IndieCorners","np":21,"mn":"Pseudo/0.20784385308943532","ix":1,"en":1,"ef":[{"ty":7,"nm":"Align","mn":"Pseudo/0.20784385308943532-0001","ix":1,"v":{"a":0,"k":8}},{"ty":6,"nm":"Size","mn":"Pseudo/0.20784385308943532-0002","ix":2,"v":0},{"ty":0,"nm":"w","mn":"Pseudo/0.20784385308943532-0003","ix":3,"v":{"a":1,"k":[{"t":149,"s":[0],"h":1},{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[8]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[38]},{"i":{"x":[0.002],"y":[1]},"o":{"x":[0.119],"y":[0]},"t":162,"s":[48]},{"t":217,"s":[64]}]}},{"ty":0,"nm":"h","mn":"Pseudo/0.20784385308943532-0004","ix":4,"v":{"a":0,"k":48}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0005","ix":5,"v":0},{"ty":6,"nm":"Rounding","mn":"Pseudo/0.20784385308943532-0006","ix":6,"v":0},{"ty":7,"nm":"Same for all corners","mn":"Pseudo/0.20784385308943532-0007","ix":7,"v":{"a":0,"k":1}},{"ty":0,"nm":"All corners","mn":"Pseudo/0.20784385308943532-0008","ix":8,"v":{"a":1,"k":[{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[80]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[24]},{"t":162,"s":[80]}]}},{"ty":0,"nm":"tl","mn":"Pseudo/0.20784385308943532-0009","ix":9,"v":{"a":0,"k":12}},{"ty":0,"nm":"tr","mn":"Pseudo/0.20784385308943532-0010","ix":10,"v":{"a":0,"k":12}},{"ty":0,"nm":"br","mn":"Pseudo/0.20784385308943532-0011","ix":11,"v":{"a":0,"k":12}},{"ty":0,"nm":"bl","mn":"Pseudo/0.20784385308943532-0012","ix":12,"v":{"a":0,"k":12}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0013","ix":13,"v":0},{"ty":6,"nm":"Alignment","mn":"Pseudo/0.20784385308943532-0014","ix":14,"v":0},{"ty":0,"nm":"X Anchor %","mn":"Pseudo/0.20784385308943532-0015","ix":15,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Anchor %","mn":"Pseudo/0.20784385308943532-0016","ix":16,"v":{"a":0,"k":0}},{"ty":0,"nm":"X Position","mn":"Pseudo/0.20784385308943532-0017","ix":17,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Position ","mn":"Pseudo/0.20784385308943532-0018","ix":18,"v":{"a":0,"k":0}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0019","ix":19,"v":0}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"k":[{"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[0,-24],[0,-24],[0,-24],[0,-24],[0,24],[0,24],[0,24],[0,24]],"c":true}],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.208,0],[0,0],[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208]],"o":[[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208],[0,0],[-2.208,0],[0,0]],"v":[[0,-20],[4,-24],[4,-24],[8,-20],[8,20],[4,24],[4,24],[0,20]],"c":true}],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.594,0],[0,0],[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594]],"o":[[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594],[0,0],[-2.594,0],[0,0]],"v":[[0,-19.3],[4.7,-24],[4.7,-24],[9.401,-19.3],[9.401,19.3],[4.7,24],[4.7,24],[0,19.3]],"c":true}],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-4.958,0],[0,0],[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958]],"o":[[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958],[0,0],[-4.958,0],[0,0]],"v":[[0,-15.017],[8.983,-24],[8.983,-24],[17.967,-15.017],[17.967,15.017],[8.983,24],[8.983,24],[0,15.017]],"c":true}],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-7.632,0],[0,0],[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632]],"o":[[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632],[0,0],[-7.632,0],[0,0]],"v":[[0,-10.171],[13.829,-24],[13.829,-24],[27.659,-10.171],[27.659,10.171],[13.829,24],[13.829,24],[0,10.171]],"c":true}],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-8.91,0],[0,0],[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91]],"o":[[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91],[0,0],[-8.91,0],[0,0]],"v":[[0,-7.856],[16.144,-24],[16.144,-24],[32.287,-7.856],[32.287,7.856],[16.144,24],[16.144,24],[0,7.856]],"c":true}],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-9.63,0],[0,0],[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63]],"o":[[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63],[0,0],[-9.63,0],[0,0]],"v":[[0,-6.551],[17.449,-24],[17.449,-24],[34.898,-6.551],[34.898,6.551],[17.449,24],[17.449,24],[0,6.551]],"c":true}],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.063,0],[0,0],[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063]],"o":[[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063],[0,0],[-10.063,0],[0,0]],"v":[[0,-5.766],[18.234,-24],[18.234,-24],[36.467,-5.766],[36.467,5.766],[18.234,24],[18.234,24],[0,5.766]],"c":true}],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.317,0],[0,0],[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317]],"o":[[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317],[0,0],[-10.317,0],[0,0]],"v":[[0,-5.306],[18.694,-24],[18.694,-24],[37.388,-5.306],[37.388,5.306],[18.694,24],[18.694,24],[0,5.306]],"c":true}],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.448,0],[0,0],[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448]],"o":[[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448],[0,0],[-10.448,0],[0,0]],"v":[[0,-5.07],[18.93,-24],[18.93,-24],[37.861,-5.07],[37.861,5.07],[18.93,24],[18.93,24],[0,5.07]],"c":true}],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.486,0],[0,0],[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486]],"o":[[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486],[0,0],[-10.486,0],[0,0]],"v":[[0,-5],[19,-24],[19,-24],[38,-5],[38,5],[19,24],[19,24],[0,5]],"c":true}],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-12.154,0],[0,0],[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154]],"o":[[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154],[0,0],[-12.154,0],[0,0]],"v":[[0,-1.977],[22.023,-24],[22.023,-24],[44.045,-1.977],[44.045,1.977],[22.023,24],[22.023,24],[0,1.977]],"c":true}],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.079,0],[0,0],[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079]],"o":[[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079],[0,0],[-13.079,0],[0,0]],"v":[[0,-0.302],[23.698,-24],[23.698,-24],[47.396,-0.302],[47.396,0.302],[23.698,24],[23.698,24],[0,0.302]],"c":true}],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[24,-24],[48,0],[48,0],[24,24],[24,24],[0,0]],"c":true}],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[24.149,-24],[48.149,0],[48.149,0],[24.149,24],[24,24],[0,0]],"c":true}],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[24.698,-24],[48.698,0],[48.698,0],[24.698,24],[24,24],[0,0]],"c":true}],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[25.714,-24],[49.714,0],[49.714,0],[25.714,24],[24,24],[0,0]],"c":true}],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[26.973,-24],[50.973,0],[50.973,0],[26.973,24],[24,24],[0,0]],"c":true}],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[28.19,-24],[52.19,0],[52.19,0],[28.19,24],[24,24],[0,0]],"c":true}],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[29.268,-24],[53.268,0],[53.268,0],[29.268,24],[24,24],[0,0]],"c":true}],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[30.206,-24],[54.206,0],[54.206,0],[30.206,24],[24,24],[0,0]],"c":true}],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[31.028,-24],[55.028,0],[55.028,0],[31.028,24],[24,24],[0,0]],"c":true}],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[31.755,-24],[55.755,0],[55.755,0],[31.755,24],[24,24],[0,0]],"c":true}],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[32.405,-24],[56.405,0],[56.405,0],[32.405,24],[24,24],[0,0]],"c":true}],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[32.99,-24],[56.99,0],[56.99,0],[32.99,24],[24,24],[0,0]],"c":true}],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[33.522,-24],[57.522,0],[57.522,0],[33.522,24],[24,24],[0,0]],"c":true}],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[34.006,-24],[58.006,0],[58.006,0],[34.006,24],[24,24],[0,0]],"c":true}],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[34.451,-24],[58.451,0],[58.451,0],[34.451,24],[24,24],[0,0]],"c":true}],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[34.861,-24],[58.861,0],[58.861,0],[34.861,24],[24,24],[0,0]],"c":true}],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[35.24,-24],[59.24,0],[59.24,0],[35.24,24],[24,24],[0,0]],"c":true}],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[35.591,-24],[59.591,0],[59.591,0],[35.591,24],[24,24],[0,0]],"c":true}],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[35.917,-24],[59.917,0],[59.917,0],[35.917,24],[24,24],[0,0]],"c":true}],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[36.221,-24],[60.221,0],[60.221,0],[36.221,24],[24,24],[0,0]],"c":true}],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[36.504,-24],[60.504,0],[60.504,0],[36.504,24],[24,24],[0,0]],"c":true}],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[36.769,-24],[60.769,0],[60.769,0],[36.769,24],[24,24],[0,0]],"c":true}],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.017,-24],[61.017,0],[61.017,0],[37.017,24],[24,24],[0,0]],"c":true}],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.248,-24],[61.248,0],[61.248,0],[37.248,24],[24,24],[0,0]],"c":true}],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.465,-24],[61.465,0],[61.465,0],[37.465,24],[24,24],[0,0]],"c":true}],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.669,-24],[61.669,0],[61.669,0],[37.669,24],[24,24],[0,0]],"c":true}],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.859,-24],[61.859,0],[61.859,0],[37.859,24],[24,24],[0,0]],"c":true}],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.038,-24],[62.038,0],[62.038,0],[38.038,24],[24,24],[0,0]],"c":true}],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.205,-24],[62.205,0],[62.205,0],[38.205,24],[24,24],[0,0]],"c":true}],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.362,-24],[62.362,0],[62.362,0],[38.362,24],[24,24],[0,0]],"c":true}],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.509,-24],[62.509,0],[62.509,0],[38.509,24],[24,24],[0,0]],"c":true}],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.647,-24],[62.647,0],[62.647,0],[38.647,24],[24,24],[0,0]],"c":true}],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.776,-24],[62.776,0],[62.776,0],[38.776,24],[24,24],[0,0]],"c":true}],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.896,-24],[62.896,0],[62.896,0],[38.896,24],[24,24],[0,0]],"c":true}],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.008,-24],[63.008,0],[63.008,0],[39.008,24],[24,24],[0,0]],"c":true}],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.113,-24],[63.113,0],[63.113,0],[39.113,24],[24,24],[0,0]],"c":true}],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.21,-24],[63.21,0],[63.21,0],[39.21,24],[24,24],[0,0]],"c":true}],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.3,-24],[63.3,0],[63.3,0],[39.3,24],[24,24],[0,0]],"c":true}],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.384,-24],[63.384,0],[63.384,0],[39.384,24],[24,24],[0,0]],"c":true}],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.461,-24],[63.461,0],[63.461,0],[39.461,24],[24,24],[0,0]],"c":true}],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.532,-24],[63.532,0],[63.532,0],[39.532,24],[24,24],[0,0]],"c":true}],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.597,-24],[63.597,0],[63.597,0],[39.597,24],[24,24],[0,0]],"c":true}],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.657,-24],[63.657,0],[63.657,0],[39.657,24],[24,24],[0,0]],"c":true}],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.711,-24],[63.711,0],[63.711,0],[39.711,24],[24,24],[0,0]],"c":true}],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.76,-24],[63.76,0],[63.76,0],[39.76,24],[24,24],[0,0]],"c":true}],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.804,-24],[63.804,0],[63.804,0],[39.804,24],[24,24],[0,0]],"c":true}],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.843,-24],[63.843,0],[63.843,0],[39.843,24],[24,24],[0,0]],"c":true}],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.877,-24],[63.877,0],[63.877,0],[39.877,24],[24,24],[0,0]],"c":true}],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.907,-24],[63.907,0],[63.907,0],[39.907,24],[24,24],[0,0]],"c":true}],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.932,-24],[63.932,0],[63.932,0],[39.932,24],[24,24],[0,0]],"c":true}],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.971,-24],[63.971,0],[63.971,0],[39.971,24],[24,24],[0,0]],"c":true}],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"k":[{"s":[0,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":450,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"IndieCorners Shape","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":249,"s":[100]},{"t":255,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,0,0],"t":123,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0,0],"t":124,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.001,0,0],"t":125,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.005,0,0],"t":126,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.013,0,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.029,0,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.054,0,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.089,0,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.134,0,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.193,0,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.267,0,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.358,0,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.466,0,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.593,0,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.739,0,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.903,0,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.054,0,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.22,0,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.403,0,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.602,0,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.821,0,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.059,0,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.319,0,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.601,0,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.909,0,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.242,0,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.604,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.998,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[4.427,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[4.897,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[5.407,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[5.965,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[6.576,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[7.246,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[7.983,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[8.8,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[9.701,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[10.699,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[11.808,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[13.041,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[14.414,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[15.945,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[17.621,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[19.429,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[21.324,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.241,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[25.111,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[26.859,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.457,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.897,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.185,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[32.333,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[33.36,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[34.272,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[35.088,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[35.82,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.479,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.076,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.613,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.099,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.538,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.937,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.299,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.629,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.927,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.198,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.442,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.663,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.862,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.041,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.2,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.342,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.467,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.577,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.672,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.754,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.822,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.879,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.925,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.961,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}]}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"right circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[-41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"left circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"size","bm":0,"hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"pb:scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"k":[{"s":[277.263,197.5,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.43,197.5,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.681,197.5,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.85,197.5,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.058,197.5,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.313,197.5,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.63,197.5,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.023,197.5,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.517,197.5,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.151,197.5,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.992,197.5,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.175,197.5,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.778,197.5,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.586,197.5,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[287.564,197.5,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[289.63,197.5,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[291.671,197.5,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[293.578,197.5,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[295.298,197.5,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[296.823,197.5,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[298.167,197.5,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[299.353,197.5,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[300.405,197.5,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[301.343,197.5,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.187,197.5,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.949,197.5,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[303.641,197.5,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.271,197.5,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.846,197.5,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.373,197.5,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.858,197.5,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.306,197.5,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.72,197.5,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.103,197.5,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.459,197.5,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.789,197.5,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.096,197.5,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.382,197.5,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.648,197.5,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.895,197.5,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.126,197.5,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.34,197.5,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.54,197.5,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.726,197.5,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.901,197.5,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.063,197.5,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.352,197.5,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.599,197.5,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.903,197.5,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.196,197.5,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.191,197.5,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.194,197.5,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.275,197.5,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.841,197.5,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[297.7,197.5,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[289.568,197.5,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.993,197.5,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.914,197.5,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.504,197.5,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[281.464,197.5,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.661,197.5,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.021,197.5,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.499,197.5,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.068,197.5,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.705,197.5,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.401,197.5,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.143,197.5,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.925,197.5,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.741,197.5,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.585,197.5,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.345,197.5,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.074,197.5,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"k":[{"s":[99.914,99.914,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.848,99.848,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.751,99.751,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.685,99.685,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.605,99.605,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.507,99.507,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.387,99.387,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.239,99.239,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.056,99.056,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.829,98.829,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.542,98.542,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.174,98.174,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.686,97.686,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97,97,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.071,96.071,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.025,95.025,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.878,93.878,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.678,92.678,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.495,91.495,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.39,90.39,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[89.393,89.393,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.508,88.508,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.729,87.729,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.041,87.041,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[86.43,86.43,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.886,85.886,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.397,85.397,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.956,84.956,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.555,84.555,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.191,84.191,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.857,83.857,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.552,83.552,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.271,83.271,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.011,83.011,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.771,82.771,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.549,82.549,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.342,82.342,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.151,82.151,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.973,81.973,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.807,81.807,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.653,81.653,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.51,81.51,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.376,81.376,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.251,81.251,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.135,81.135,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.027,81.027,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.926,80.926,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.833,80.833,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.746,80.746,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.665,80.665,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.591,80.591,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.522,80.522,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.458,80.458,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.4,80.4,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.346,80.346,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.298,80.298,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.253,80.253,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.176,80.176,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.115,80.115,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.049,80.049,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.179,80.179,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.757,80.757,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.87,81.87,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.86,83.86,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88,88,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.714,92.714,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.789,94.789,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.992,95.992,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.809,96.809,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.412,97.412,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.878,97.878,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.249,98.249,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.553,98.553,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.803,98.803,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.012,99.012,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.188,99.188,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.337,99.337,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.464,99.464,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.661,99.661,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.737,99.737,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.8,99.8,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.896,99.896,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.99,99.99,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":256,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":7}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277.263,197.5],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.43,197.5],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.681,197.5],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.85,197.5],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.058,197.5],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.313,197.5],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.63,197.5],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.023,197.5],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.517,197.5],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.151,197.5],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.992,197.5],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.175,197.5],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.778,197.5],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.586,197.5],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[287.564,197.5],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[289.63,197.5],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[291.671,197.5],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[293.578,197.5],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[295.298,197.5],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[296.823,197.5],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[298.167,197.5],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[299.353,197.5],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[300.405,197.5],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[301.343,197.5],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.187,197.5],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.949,197.5],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[303.641,197.5],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.271,197.5],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.846,197.5],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.373,197.5],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.858,197.5],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.306,197.5],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.72,197.5],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.103,197.5],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.459,197.5],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.789,197.5],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.096,197.5],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.382,197.5],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.648,197.5],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.895,197.5],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.126,197.5],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.34,197.5],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.54,197.5],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.726,197.5],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.901,197.5],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.063,197.5],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.352,197.5],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.599,197.5],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.903,197.5],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.196,197.5],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.5,197.5],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.936,197.788],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.7,199.014],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.071,202.033],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[298.438,206.77],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[293.978,211.581],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[290.807,215.785],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[288.487,219.444],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[286.718,222.659],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.317,225.519],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[284.171,228.085],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.211,230.396],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.392,232.474],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[281.682,234.334],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[281.059,235.992],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.506,237.461],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.012,238.754],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.568,239.881],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.169,240.855],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.809,241.684],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.487,242.379],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.199,242.951],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.943,243.409],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.72,243.76],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.528,243.874],"t":274,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.369,243.701],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.24,243.336],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.142,242.847],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.073,242.284],"t":278,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.033,241.684],"t":279,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,241.075],"t":280,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,240.497],"t":281,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.98],"t":282,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.538],"t":283,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.181],"t":284,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,238.917],"t":285,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.065],"t":293,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.265],"t":295,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.455],"t":297,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.685],"t":300,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.85,239.729],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.285,239.199],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.162,238.218],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.05,236.594],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[267.986,234.04],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[265.592,226.983],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.166,217.207],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[272.184,212.309],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.238,209.328],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.904,207.237],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.379,205.654],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.741,204.399],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.029,203.375],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.267,202.521],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.466,201.799],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.638,201.182],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.788,200.65],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.921,200.189],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.041,199.789],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.149,199.439],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.246,199.134],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.337,198.867],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.419,198.634],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.495,198.431],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.566,198.255],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.632,198.103],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.692,197.973],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.748,197.862],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.86,197.692],"t":410,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":280,"s":[30,30]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":280,"s":[30]},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450982481,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"matte","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":250,"s":[0,0,0],"to":[-28.906,14.531,0],"ti":[7.183,-8.833,0]},{"t":280,"s":[-43.1,53,0],"h":1},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":380,"s":[-43.1,53,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":386,"s":[-25.86,31.8,0],"to":[7.183,-8.833,0],"ti":[-7.167,9.833,0]},{"t":416,"s":[0,0,0]}]},"a":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":255,"s":[0,0,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.573,"y":1},"o":{"x":0.236,"y":0},"t":273,"s":[0,-6,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":287,"s":[0,1.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":307,"s":[0,0,0]}]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":280,"s":[30,30]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":280,"s":[30]},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"Back_LofiApp","parent":9,"tt":1,"tp":9,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":280,"s":[10,10,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[10,10,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[46,46,100]},{"t":416,"s":[100,100,100]}]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"behindApp","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":259,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":386,"s":[0]},{"t":397,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[503.5,314.5]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"Back_LofiLauncher","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":25,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":450,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[554,564]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"container for media","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Back_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[339.937,151.75,0]},"a":{"a":0,"k":[339.937,151.75,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[334,279]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[334,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":16},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,171.125,0]},"a":{"a":0,"k":[82,171.125,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 2","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,140,0]},"a":{"a":0,"k":[82,140.938,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Search","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"header","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,171]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"block","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app only","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Back_LofiLauncher","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,117.5,0]},"a":{"a":0,"k":[252,275,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":25,"nm":"Drop Shadow","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 2","np":8,"mn":"ADBE Drop Shadow","ix":2,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 3","np":8,"mn":"ADBE Drop Shadow","ix":3,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 4","np":8,"mn":"ADBE Drop Shadow","ix":4,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 5","np":8,"mn":"ADBE Drop Shadow","ix":5,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 6","np":8,"mn":"ADBE Drop Shadow","ix":6,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 7","np":8,"mn":"ADBE Drop Shadow","ix":7,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 8","np":8,"mn":"ADBE Drop Shadow","ix":8,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 9","np":8,"mn":"ADBE Drop Shadow","ix":9,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 10","np":8,"mn":"ADBE Drop Shadow","ix":10,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 11","np":8,"mn":"ADBE Drop Shadow","ix":11,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 12","np":8,"mn":"ADBE Drop Shadow","ix":12,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]}],"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[300,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[168,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":15},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[132,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":25,"nm":"Drop Shadow","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 2","np":8,"mn":"ADBE Drop Shadow","ix":2,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 3","np":8,"mn":"ADBE Drop Shadow","ix":3,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 4","np":8,"mn":"ADBE Drop Shadow","ix":4,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]}],"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[20.144,20.144],[20.144,-20.144],[0,0],[-20.144,-20.144],[-20.144,20.144],[0,0]],"o":[[-20.144,-20.144],[0,0],[-20.144,20.144],[20.144,20.144],[0,0],[20.144,-20.144]],"v":[[44.892,-44.892],[-28.057,-44.892],[-44.892,-28.057],[-44.892,44.892],[28.057,44.892],[44.892,28.057]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":15},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[108,152.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets weather","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.782,-2.684],[0,0],[2.63,-0.033],[0,0],[2.807,-4.716],[0,0],[2.263,-1.343],[0,0],[0.066,-5.485],[0,0],[1.292,-2.295],[0,0],[-2.683,-4.784],[0,0],[-0.033,-2.63],[0,0],[-4.716,-2.807],[0,0],[-1.338,-2.263],[0,0],[-5.483,-0.066],[0,0],[-2.296,-1.292],[0,0],[-4.782,2.683],[0,0],[-2.63,0.033],[0,0],[-2.807,4.716],[0,0],[-2.263,1.338],[0,0],[-0.066,5.483],[0,0],[-1.292,2.295],[0,0],[2.683,4.784],[0,0],[0.033,2.631],[0,0],[4.716,2.801],[0,0],[1.338,2.262],[0,0],[5.483,0.068],[0,0],[2.296,1.287]],"o":[[-4.782,-2.684],[0,0],[-2.296,1.287],[0,0],[-5.483,0.068],[0,0],[-1.338,2.262],[0,0],[-4.716,2.801],[0,0],[-0.033,2.631],[0,0],[-2.683,4.784],[0,0],[1.292,2.295],[0,0],[0.066,5.483],[0,0],[2.263,1.338],[0,0],[2.807,4.716],[0,0],[2.63,0.033],[0,0],[4.782,2.683],[0,0],[2.296,-1.292],[0,0],[5.483,-0.066],[0,0],[1.338,-2.263],[0,0],[4.716,-2.807],[0,0],[0.033,-2.63],[0,0],[2.683,-4.784],[0,0],[-1.292,-2.295],[0,0],[-0.066,-5.485],[0,0],[-2.263,-1.343],[0,0],[-2.807,-4.716],[0,0],[-2.63,-0.033],[0,0]],"v":[[7.7,-57.989],[-7.7,-57.989],[-11.019,-56.128],[-18.523,-54.117],[-22.327,-54.07],[-35.668,-46.369],[-37.609,-43.1],[-43.099,-37.605],[-46.372,-35.663],[-54.072,-22.324],[-54.118,-18.522],[-56.132,-11.016],[-57.988,-7.7],[-57.988,7.703],[-56.132,11.019],[-54.118,18.524],[-54.072,22.328],[-46.372,35.669],[-43.099,37.611],[-37.609,43.101],[-35.668,46.373],[-22.327,54.074],[-18.523,54.12],[-11.019,56.133],[-7.7,57.99],[7.7,57.99],[11.019,56.133],[18.523,54.12],[22.327,54.074],[35.668,46.373],[37.609,43.101],[43.099,37.611],[46.372,35.669],[54.072,22.328],[54.118,18.524],[56.132,11.019],[57.988,7.703],[57.988,-7.7],[56.132,-11.016],[54.118,-18.522],[54.072,-22.324],[46.372,-35.663],[43.099,-37.605],[37.609,-43.1],[35.668,-46.369],[22.327,-54.07],[18.523,-54.117],[11.019,-56.128]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":15},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,104.003]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets clock","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":25,"nm":"Drop Shadow","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 2","np":8,"mn":"ADBE Drop Shadow","ix":2,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 3","np":8,"mn":"ADBE Drop Shadow","ix":3,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 4","np":8,"mn":"ADBE Drop Shadow","ix":4,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 5","np":8,"mn":"ADBE Drop Shadow","ix":5,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 6","np":8,"mn":"ADBE Drop Shadow","ix":6,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 7","np":8,"mn":"ADBE Drop Shadow","ix":7,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 8","np":8,"mn":"ADBE Drop Shadow","ix":8,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 9","np":8,"mn":"ADBE Drop Shadow","ix":9,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 10","np":8,"mn":"ADBE Drop Shadow","ix":10,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 11","np":8,"mn":"ADBE Drop Shadow","ix":11,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 12","np":8,"mn":"ADBE Drop Shadow","ix":12,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 13","np":8,"mn":"ADBE Drop Shadow","ix":13,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 14","np":8,"mn":"ADBE Drop Shadow","ix":14,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]}],"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 7","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,128.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,56.002]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[156,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[60,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Scale Up","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":250,"s":[85,85,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":256,"s":[91,91,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":286,"s":[100,100,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[96,96,100]},{"t":416,"s":[90,90,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"Back_RightDismiss","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"release Scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":0,"k":476},"y":{"a":0,"k":197}},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.08,0.08,0.08],"y":[0.47,0.47,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.999,0.999,0.999],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":254,"s":[105,105,100]},{"t":266,"s":[50,50,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiary","cl":"onTertiary","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":151,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":255,"s":[100]},{"t":258,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[-0.692,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.692,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.392,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.675,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.521,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-16.835,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-18.141,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-18.925,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.386,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.622,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.692,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-22.714,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-24.39,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-24.692,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-24.766,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.041,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.549,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-26.178,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-26.787,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-27.326,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-27.795,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.206,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.57,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.894,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.187,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.453,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.695,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.917,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.122,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.312,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.487,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.65,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.802,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.944,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.076,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.2,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.316,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.424,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.526,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.621,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.711,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.795,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.873,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.947,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.015,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.08,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.14,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.196,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.248,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.297,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.342,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.384,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.422,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.458,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.49,0,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.52,0,0],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.547,0,0],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.572,0,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.594,0,0],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.613,0,0],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.645,0,0],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.677,0,0],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.36,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[5.459,5.2],[-3.459,0],[5.459,-5.2]],"c":false}]},{"i":{"x":0.02,"y":1},"o":{"x":0.167,"y":0.167},"t":152,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.779,4.88],[-3.459,0],[4.779,-4.88]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":159,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.12,"y":0},"t":162,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"t":217,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,9.2],[-3.459,0],[3.459,-9.2]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.121568627656,0.211764708161,0.101960785687,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":4},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Vector 1","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":257,"s":[100]},{"t":260,"s":[0]}]},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.06],"y":[-0.15]},"t":160,"s":[13.981]},{"t":189,"s":[-0.019]}]},"y":{"a":0,"k":0}},"a":{"a":0,"k":[-31.019,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"IndieCorners","np":21,"mn":"Pseudo/0.20784385308943532","ix":1,"en":1,"ef":[{"ty":7,"nm":"Align","mn":"Pseudo/0.20784385308943532-0001","ix":1,"v":{"a":0,"k":4}},{"ty":6,"nm":"Size","mn":"Pseudo/0.20784385308943532-0002","ix":2,"v":0},{"ty":0,"nm":"w","mn":"Pseudo/0.20784385308943532-0003","ix":3,"v":{"a":1,"k":[{"t":149,"s":[0],"h":1},{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[8]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[38]},{"i":{"x":[0.002],"y":[1]},"o":{"x":[0.119],"y":[0]},"t":162,"s":[48]},{"t":217,"s":[64]}]}},{"ty":0,"nm":"h","mn":"Pseudo/0.20784385308943532-0004","ix":4,"v":{"a":0,"k":48}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0005","ix":5,"v":0},{"ty":6,"nm":"Rounding","mn":"Pseudo/0.20784385308943532-0006","ix":6,"v":0},{"ty":7,"nm":"Same for all corners","mn":"Pseudo/0.20784385308943532-0007","ix":7,"v":{"a":0,"k":1}},{"ty":0,"nm":"All corners","mn":"Pseudo/0.20784385308943532-0008","ix":8,"v":{"a":1,"k":[{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[80]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[24]},{"t":162,"s":[80]}]}},{"ty":0,"nm":"tl","mn":"Pseudo/0.20784385308943532-0009","ix":9,"v":{"a":0,"k":12}},{"ty":0,"nm":"tr","mn":"Pseudo/0.20784385308943532-0010","ix":10,"v":{"a":0,"k":12}},{"ty":0,"nm":"br","mn":"Pseudo/0.20784385308943532-0011","ix":11,"v":{"a":0,"k":12}},{"ty":0,"nm":"bl","mn":"Pseudo/0.20784385308943532-0012","ix":12,"v":{"a":0,"k":12}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0013","ix":13,"v":0},{"ty":6,"nm":"Alignment","mn":"Pseudo/0.20784385308943532-0014","ix":14,"v":0},{"ty":0,"nm":"X Anchor %","mn":"Pseudo/0.20784385308943532-0015","ix":15,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Anchor %","mn":"Pseudo/0.20784385308943532-0016","ix":16,"v":{"a":0,"k":0}},{"ty":0,"nm":"X Position","mn":"Pseudo/0.20784385308943532-0017","ix":17,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Position ","mn":"Pseudo/0.20784385308943532-0018","ix":18,"v":{"a":0,"k":0}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0019","ix":19,"v":0}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"k":[{"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[0,-24],[0,-24],[0,-24],[0,-24],[0,24],[0,24],[0,24],[0,24]],"c":true}],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.208,0],[0,0],[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208]],"o":[[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208],[0,0],[-2.208,0],[0,0]],"v":[[-8,-20],[-4,-24],[-4,-24],[0,-20],[0,20],[-4,24],[-4,24],[-8,20]],"c":true}],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.594,0],[0,0],[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594]],"o":[[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594],[0,0],[-2.594,0],[0,0]],"v":[[-9.401,-19.3],[-4.7,-24],[-4.7,-24],[0,-19.3],[0,19.3],[-4.7,24],[-4.7,24],[-9.401,19.3]],"c":true}],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-4.958,0],[0,0],[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958]],"o":[[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958],[0,0],[-4.958,0],[0,0]],"v":[[-17.967,-15.017],[-8.983,-24],[-8.983,-24],[0,-15.017],[0,15.017],[-8.983,24],[-8.983,24],[-17.967,15.017]],"c":true}],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-7.632,0],[0,0],[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632]],"o":[[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632],[0,0],[-7.632,0],[0,0]],"v":[[-27.659,-10.171],[-13.829,-24],[-13.829,-24],[0,-10.171],[0,10.171],[-13.829,24],[-13.829,24],[-27.659,10.171]],"c":true}],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-8.91,0],[0,0],[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91]],"o":[[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91],[0,0],[-8.91,0],[0,0]],"v":[[-32.287,-7.856],[-16.144,-24],[-16.144,-24],[0,-7.856],[0,7.856],[-16.144,24],[-16.144,24],[-32.287,7.856]],"c":true}],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-9.63,0],[0,0],[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63]],"o":[[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63],[0,0],[-9.63,0],[0,0]],"v":[[-34.898,-6.551],[-17.449,-24],[-17.449,-24],[0,-6.551],[0,6.551],[-17.449,24],[-17.449,24],[-34.898,6.551]],"c":true}],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.063,0],[0,0],[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063]],"o":[[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063],[0,0],[-10.063,0],[0,0]],"v":[[-36.467,-5.766],[-18.234,-24],[-18.234,-24],[0,-5.766],[0,5.766],[-18.234,24],[-18.234,24],[-36.467,5.766]],"c":true}],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.317,0],[0,0],[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317]],"o":[[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317],[0,0],[-10.317,0],[0,0]],"v":[[-37.388,-5.306],[-18.694,-24],[-18.694,-24],[0,-5.306],[0,5.306],[-18.694,24],[-18.694,24],[-37.388,5.306]],"c":true}],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.448,0],[0,0],[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448]],"o":[[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448],[0,0],[-10.448,0],[0,0]],"v":[[-37.861,-5.07],[-18.93,-24],[-18.93,-24],[0,-5.07],[0,5.07],[-18.93,24],[-18.93,24],[-37.861,5.07]],"c":true}],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.486,0],[0,0],[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486]],"o":[[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486],[0,0],[-10.486,0],[0,0]],"v":[[-38,-5],[-19,-24],[-19,-24],[0,-5],[0,5],[-19,24],[-19,24],[-38,5]],"c":true}],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-12.154,0],[0,0],[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154]],"o":[[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154],[0,0],[-12.154,0],[0,0]],"v":[[-44.045,-1.977],[-22.023,-24],[-22.023,-24],[0,-1.977],[0,1.977],[-22.023,24],[-22.023,24],[-44.045,1.977]],"c":true}],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.079,0],[0,0],[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079]],"o":[[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079],[0,0],[-13.079,0],[0,0]],"v":[[-47.396,-0.302],[-23.698,-24],[-23.698,-24],[0,-0.302],[0,0.302],[-23.698,24],[-23.698,24],[-47.396,0.302]],"c":true}],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-48,0],[-24,-24],[-24,-24],[0,0],[0,0],[-24,24],[-24,24],[-48,0]],"c":true}],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-48.149,0],[-24.149,-24],[-24,-24],[0,0],[0,0],[-24,24],[-24.149,24],[-48.149,0]],"c":true}],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-48.698,0],[-24.698,-24],[-24,-24],[0,0],[0,0],[-24,24],[-24.698,24],[-48.698,0]],"c":true}],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-49.714,0],[-25.714,-24],[-24,-24],[0,0],[0,0],[-24,24],[-25.714,24],[-49.714,0]],"c":true}],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-50.973,0],[-26.973,-24],[-24,-24],[0,0],[0,0],[-24,24],[-26.973,24],[-50.973,0]],"c":true}],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-52.19,0],[-28.19,-24],[-24,-24],[0,0],[0,0],[-24,24],[-28.19,24],[-52.19,0]],"c":true}],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-53.268,0],[-29.268,-24],[-24,-24],[0,0],[0,0],[-24,24],[-29.268,24],[-53.268,0]],"c":true}],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-54.206,0],[-30.206,-24],[-24,-24],[0,0],[0,0],[-24,24],[-30.206,24],[-54.206,0]],"c":true}],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-55.028,0],[-31.028,-24],[-24,-24],[0,0],[0,0],[-24,24],[-31.028,24],[-55.028,0]],"c":true}],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-55.755,0],[-31.755,-24],[-24,-24],[0,0],[0,0],[-24,24],[-31.755,24],[-55.755,0]],"c":true}],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-56.405,0],[-32.405,-24],[-24,-24],[0,0],[0,0],[-24,24],[-32.405,24],[-56.405,0]],"c":true}],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-56.99,0],[-32.99,-24],[-24,-24],[0,0],[0,0],[-24,24],[-32.99,24],[-56.99,0]],"c":true}],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-57.522,0],[-33.522,-24],[-24,-24],[0,0],[0,0],[-24,24],[-33.522,24],[-57.522,0]],"c":true}],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-58.006,0],[-34.006,-24],[-24,-24],[0,0],[0,0],[-24,24],[-34.006,24],[-58.006,0]],"c":true}],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-58.451,0],[-34.451,-24],[-24,-24],[0,0],[0,0],[-24,24],[-34.451,24],[-58.451,0]],"c":true}],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-58.861,0],[-34.861,-24],[-24,-24],[0,0],[0,0],[-24,24],[-34.861,24],[-58.861,0]],"c":true}],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-59.24,0],[-35.24,-24],[-24,-24],[0,0],[0,0],[-24,24],[-35.24,24],[-59.24,0]],"c":true}],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-59.591,0],[-35.591,-24],[-24,-24],[0,0],[0,0],[-24,24],[-35.591,24],[-59.591,0]],"c":true}],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-59.917,0],[-35.917,-24],[-24,-24],[0,0],[0,0],[-24,24],[-35.917,24],[-59.917,0]],"c":true}],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-60.221,0],[-36.221,-24],[-24,-24],[0,0],[0,0],[-24,24],[-36.221,24],[-60.221,0]],"c":true}],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-60.504,0],[-36.504,-24],[-24,-24],[0,0],[0,0],[-24,24],[-36.504,24],[-60.504,0]],"c":true}],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-60.769,0],[-36.769,-24],[-24,-24],[0,0],[0,0],[-24,24],[-36.769,24],[-60.769,0]],"c":true}],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.017,0],[-37.017,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.017,24],[-61.017,0]],"c":true}],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.248,0],[-37.248,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.248,24],[-61.248,0]],"c":true}],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.465,0],[-37.465,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.465,24],[-61.465,0]],"c":true}],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.669,0],[-37.669,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.669,24],[-61.669,0]],"c":true}],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.859,0],[-37.859,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.859,24],[-61.859,0]],"c":true}],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.038,0],[-38.038,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.038,24],[-62.038,0]],"c":true}],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.205,0],[-38.205,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.205,24],[-62.205,0]],"c":true}],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.362,0],[-38.362,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.362,24],[-62.362,0]],"c":true}],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.509,0],[-38.509,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.509,24],[-62.509,0]],"c":true}],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.647,0],[-38.647,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.647,24],[-62.647,0]],"c":true}],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.776,0],[-38.776,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.776,24],[-62.776,0]],"c":true}],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.896,0],[-38.896,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.896,24],[-62.896,0]],"c":true}],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.008,0],[-39.008,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.008,24],[-63.008,0]],"c":true}],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.113,0],[-39.113,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.113,24],[-63.113,0]],"c":true}],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.21,0],[-39.21,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.21,24],[-63.21,0]],"c":true}],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.3,0],[-39.3,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.3,24],[-63.3,0]],"c":true}],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.384,0],[-39.384,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.384,24],[-63.384,0]],"c":true}],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.461,0],[-39.461,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.461,24],[-63.461,0]],"c":true}],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.532,0],[-39.532,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.532,24],[-63.532,0]],"c":true}],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.597,0],[-39.597,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.597,24],[-63.597,0]],"c":true}],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.657,0],[-39.657,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.657,24],[-63.657,0]],"c":true}],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.711,0],[-39.711,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.711,24],[-63.711,0]],"c":true}],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.76,0],[-39.76,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.76,24],[-63.76,0]],"c":true}],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.804,0],[-39.804,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.804,24],[-63.804,0]],"c":true}],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.843,0],[-39.843,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.843,24],[-63.843,0]],"c":true}],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.877,0],[-39.877,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.877,24],[-63.877,0]],"c":true}],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.907,0],[-39.907,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.907,24],[-63.907,0]],"c":true}],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.932,0],[-39.932,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.932,24],[-63.932,0]],"c":true}],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.971,0],[-39.971,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.971,24],[-63.971,0]],"c":true}],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"k":[{"s":[0,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":498,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"IndieCorners Shape","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":249,"s":[100]},{"t":255,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,0,0],"t":123,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0,0],"t":124,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.001,0,0],"t":125,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.005,0,0],"t":126,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.013,0,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.029,0,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.054,0,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.089,0,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.134,0,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.193,0,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.267,0,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.358,0,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.466,0,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.593,0,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.739,0,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.903,0,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.054,0,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.22,0,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.403,0,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.602,0,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.821,0,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.059,0,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.319,0,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.601,0,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.909,0,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.242,0,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.604,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.998,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.427,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.897,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.407,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.965,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-6.576,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.246,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.983,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-8.8,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.701,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.699,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.808,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.041,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.414,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-15.945,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-17.621,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.429,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-21.324,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-23.241,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.111,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-26.859,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.457,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.897,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.185,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.333,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-33.36,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-34.272,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-35.088,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-35.82,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.479,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-37.076,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-37.613,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.099,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.538,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.937,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.299,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.629,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.927,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.198,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.442,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.663,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.862,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.041,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.2,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.342,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.467,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.577,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.672,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.754,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.822,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.879,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.925,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.961,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}]}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"right circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[-41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"left circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"size","bm":0,"hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"pb:scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"k":[{"s":[276.737,197.5,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.57,197.5,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.319,197.5,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.15,197.5,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.942,197.5,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.687,197.5,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.37,197.5,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.978,197.5,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.484,197.5,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.85,197.5,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.008,197.5,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.825,197.5,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.222,197.5,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[268.416,197.5,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[266.436,197.5,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[264.37,197.5,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[262.33,197.5,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[260.423,197.5,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[258.703,197.5,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[257.178,197.5,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[255.833,197.5,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[254.646,197.5,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[253.594,197.5,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252.657,197.5,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.814,197.5,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.052,197.5,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[250.36,197.5,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.73,197.5,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.154,197.5,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.627,197.5,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.142,197.5,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.694,197.5,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.28,197.5,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.897,197.5,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.541,197.5,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.211,197.5,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.904,197.5,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.619,197.5,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.353,197.5,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.107,197.5,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.876,197.5,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.661,197.5,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.461,197.5,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.274,197.5,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.099,197.5,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.937,197.5,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.787,197.5,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.648,197.5,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.52,197.5,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.291,197.5,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.098,197.5,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.866,197.5,0],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.655,197.5,0],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.5,197.5,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.809,197.5,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.805,197.5,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.726,197.5,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.159,197.5,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[256.3,197.5,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[264.431,197.5,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[268.009,197.5,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.087,197.5,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.496,197.5,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[272.536,197.5,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.339,197.5,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.98,197.5,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.502,197.5,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.933,197.5,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.295,197.5,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.599,197.5,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.857,197.5,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.075,197.5,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.259,197.5,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.415,197.5,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.655,197.5,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.926,197.5,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"k":[{"s":[99.914,99.914,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.848,99.848,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.751,99.751,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.685,99.685,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.605,99.605,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.507,99.507,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.387,99.387,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.239,99.239,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.056,99.056,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.829,98.829,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.542,98.542,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.174,98.174,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.686,97.686,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97,97,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.071,96.071,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.025,95.025,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.878,93.878,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.678,92.678,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.495,91.495,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.39,90.39,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[89.393,89.393,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.508,88.508,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.729,87.729,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.041,87.041,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[86.43,86.43,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.886,85.886,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.397,85.397,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.956,84.956,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.555,84.555,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.191,84.191,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.857,83.857,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.552,83.552,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.271,83.271,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.011,83.011,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.771,82.771,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.549,82.549,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.342,82.342,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.151,82.151,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.973,81.973,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.807,81.807,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.653,81.653,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.51,81.51,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.376,81.376,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.251,81.251,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.135,81.135,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.027,81.027,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.926,80.926,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.833,80.833,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.746,80.746,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.665,80.665,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.591,80.591,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.522,80.522,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.458,80.458,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.4,80.4,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.346,80.346,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.298,80.298,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.253,80.253,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.176,80.176,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.115,80.115,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.049,80.049,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.179,80.179,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.757,80.757,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.87,81.87,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.86,83.86,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88,88,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.714,92.714,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.789,94.789,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.992,95.992,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.809,96.809,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.412,97.412,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.878,97.878,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.249,98.249,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.553,98.553,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.803,98.803,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.012,99.012,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.188,99.188,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.337,99.337,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.464,99.464,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.661,99.661,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.737,99.737,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.8,99.8,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.896,99.896,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.99,99.99,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":256,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":7}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[276.737,197.5],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.57,197.5],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.319,197.5],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.15,197.5],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.942,197.5],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.687,197.5],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.37,197.5],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.978,197.5],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.484,197.5],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.85,197.5],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.008,197.5],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.825,197.5],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.222,197.5],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[268.416,197.5],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[266.436,197.5],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[264.37,197.5],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[262.33,197.5],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[260.423,197.5],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[258.703,197.5],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[257.178,197.5],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[255.833,197.5],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[254.646,197.5],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[253.594,197.5],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252.657,197.5],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.814,197.5],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.052,197.5],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[250.36,197.5],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.73,197.5],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.154,197.5],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.627,197.5],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.142,197.5],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.694,197.5],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.28,197.5],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.897,197.5],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.541,197.5],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.211,197.5],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.904,197.5],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.619,197.5],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.353,197.5],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.107,197.5],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.876,197.5],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.661,197.5],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.461,197.5],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.274,197.5],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.099,197.5],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.937,197.5],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.787,197.5],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.648,197.5],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.52,197.5],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.291,197.5],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.098,197.5],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.866,197.5],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.655,197.5],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.5,197.5],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.159,197.525],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.792,197.842],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.233,199.672],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[257.542,204.005],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[262.216,208.989],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[265.399,213.457],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[267.667,217.355],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[269.364,220.773],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.685,223.812],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.743,226.538],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[272.606,228.991],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.321,231.197],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.92,233.179],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.427,234.953],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.858,236.532],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.225,237.926],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.539,239.152],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.808,240.219],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.038,241.138],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.233,241.918],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.398,242.568],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.537,243.099],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.653,243.517],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.824,243.574],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.884,243.254],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.928,242.802],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.959,242.269],"t":278,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.976,241.685],"t":279,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,241.075],"t":280,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,240.497],"t":281,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.98],"t":282,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.538],"t":283,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.181],"t":284,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,238.917],"t":285,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.065],"t":293,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.265],"t":295,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.455],"t":297,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.685],"t":300,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.15,239.729],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.715,239.199],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.839,238.218],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.95,236.594],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[286.015,234.04],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[288.407,226.983],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.954,217.108],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.005,212.156],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.975,209.156],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.314,207.064],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.834,205.487],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.461,204.24],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.159,203.226],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.905,202.385],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.691,201.676],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.503,201.072],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.339,200.553],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.193,200.105],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.061,199.716],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.941,199.376],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.831,199.079],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.729,198.82],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.636,198.594],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.551,198.398],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.472,198.228],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.399,198.082],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.333,197.956],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.209,197.759],"t":409,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.063,197.577],"t":412,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"t":280,"s":[30,30],"h":1},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"t":280,"s":[30],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450982481,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"matte","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":250,"s":[0,0,0],"to":[29.688,0.625,0],"ti":[0,0,0]},{"t":280,"s":[43.1,53,0],"h":1},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":380,"s":[43.1,53,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":386,"s":[25.86,31.8,0],"to":[0,0,0],"ti":[0,0,0]},{"t":416,"s":[0,0,0]}]},"a":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":255,"s":[0,0,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.573,"y":1},"o":{"x":0.236,"y":0},"t":273,"s":[0,-6,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":287,"s":[0,1.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":307,"s":[0,0,0]}]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"t":280,"s":[30,30],"h":1},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"t":280,"s":[30],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"Back_LofiApp","parent":9,"tt":1,"tp":9,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":280,"s":[10,10,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[10,10,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[46,46,100]},{"t":416,"s":[100,100,100]}]}},"ao":0,"w":504,"h":315,"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"behindApp","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":259,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":386,"s":[0]},{"t":397,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[503.5,314.5]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"Back_LofiLauncher","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":25,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":498,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[554,564]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"container for media","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Back_LeftDismiss","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[277,282,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":554,"h":564,"ip":0,"op":426,"st":-25,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Back_RightDismiss","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[277,282,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":554,"h":564,"ip":426,"op":902,"st":401,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
+{"v":"5.12.1","fr":60,"ip":0,"op":900,"w":554,"h":564,"nm":"Trackpad-JSON_BackGesture-EDU","ddd":0,"assets":[{"id":"comp_0","nm":"Back_LeftDismiss","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"release Scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":0,"k":79},"y":{"a":0,"k":197}},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.08,0.08,0.08],"y":[0.47,0.47,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.999,0.999,0.999],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":254,"s":[105,105,100]},{"t":266,"s":[50,50,100]}]}},"ao":0,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":151,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":255,"s":[100]},{"t":258,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[-0.692,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.308,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[4.009,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[8.291,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[13.138,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[15.452,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[16.757,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[17.542,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[18.002,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[18.238,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[18.308,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[21.331,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.006,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.308,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.382,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.657,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[24.165,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[24.794,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[25.403,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[25.942,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[26.411,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[26.822,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[27.186,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[27.511,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[27.803,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.069,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.311,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.534,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.739,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.928,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.103,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.267,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.419,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.56,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.693,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.816,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.932,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.041,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.142,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.238,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.327,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.411,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.489,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.563,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.632,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.696,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.756,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.812,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.864,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.913,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.958,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.039,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.074,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.107,0,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.137,0,0],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.164,0,0],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.188,0,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.21,0,0],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.23,0,0],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.247,0,0],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.274,0,0],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.305,0,0],"t":215,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.36,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[5.459,5.2],[-3.459,0],[5.459,-5.2]],"c":false}]},{"i":{"x":0.02,"y":1},"o":{"x":0.167,"y":0.167},"t":152,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.779,4.88],[-3.459,0],[4.779,-4.88]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":159,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.12,"y":0},"t":162,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"t":217,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,9.2],[-3.459,0],[3.459,-9.2]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.121568627656,0.211764708161,0.101960785687,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":4},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Vector 1","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":257,"s":[100]},{"t":260,"s":[0]}]},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.06],"y":[0.15]},"t":160,"s":[-14]},{"t":189,"s":[0]}]},"y":{"a":0,"k":0}},"a":{"a":0,"k":[32,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"IndieCorners","np":21,"mn":"Pseudo/0.20784385308943532","ix":1,"en":1,"ef":[{"ty":7,"nm":"Align","mn":"Pseudo/0.20784385308943532-0001","ix":1,"v":{"a":0,"k":8}},{"ty":6,"nm":"Size","mn":"Pseudo/0.20784385308943532-0002","ix":2,"v":0},{"ty":0,"nm":"w","mn":"Pseudo/0.20784385308943532-0003","ix":3,"v":{"a":1,"k":[{"t":149,"s":[0],"h":1},{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[8]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[38]},{"i":{"x":[0.002],"y":[1]},"o":{"x":[0.119],"y":[0]},"t":162,"s":[48]},{"t":217,"s":[64]}]}},{"ty":0,"nm":"h","mn":"Pseudo/0.20784385308943532-0004","ix":4,"v":{"a":0,"k":48}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0005","ix":5,"v":0},{"ty":6,"nm":"Rounding","mn":"Pseudo/0.20784385308943532-0006","ix":6,"v":0},{"ty":7,"nm":"Same for all corners","mn":"Pseudo/0.20784385308943532-0007","ix":7,"v":{"a":0,"k":1}},{"ty":0,"nm":"All corners","mn":"Pseudo/0.20784385308943532-0008","ix":8,"v":{"a":1,"k":[{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[80]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[24]},{"t":162,"s":[80]}]}},{"ty":0,"nm":"tl","mn":"Pseudo/0.20784385308943532-0009","ix":9,"v":{"a":0,"k":12}},{"ty":0,"nm":"tr","mn":"Pseudo/0.20784385308943532-0010","ix":10,"v":{"a":0,"k":12}},{"ty":0,"nm":"br","mn":"Pseudo/0.20784385308943532-0011","ix":11,"v":{"a":0,"k":12}},{"ty":0,"nm":"bl","mn":"Pseudo/0.20784385308943532-0012","ix":12,"v":{"a":0,"k":12}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0013","ix":13,"v":0},{"ty":6,"nm":"Alignment","mn":"Pseudo/0.20784385308943532-0014","ix":14,"v":0},{"ty":0,"nm":"X Anchor %","mn":"Pseudo/0.20784385308943532-0015","ix":15,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Anchor %","mn":"Pseudo/0.20784385308943532-0016","ix":16,"v":{"a":0,"k":0}},{"ty":0,"nm":"X Position","mn":"Pseudo/0.20784385308943532-0017","ix":17,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Position ","mn":"Pseudo/0.20784385308943532-0018","ix":18,"v":{"a":0,"k":0}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0019","ix":19,"v":0}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"k":[{"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[0,-24],[0,-24],[0,-24],[0,-24],[0,24],[0,24],[0,24],[0,24]],"c":true}],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.208,0],[0,0],[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208]],"o":[[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208],[0,0],[-2.208,0],[0,0]],"v":[[0,-20],[4,-24],[4,-24],[8,-20],[8,20],[4,24],[4,24],[0,20]],"c":true}],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.594,0],[0,0],[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594]],"o":[[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594],[0,0],[-2.594,0],[0,0]],"v":[[0,-19.3],[4.7,-24],[4.7,-24],[9.401,-19.3],[9.401,19.3],[4.7,24],[4.7,24],[0,19.3]],"c":true}],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-4.958,0],[0,0],[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958]],"o":[[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958],[0,0],[-4.958,0],[0,0]],"v":[[0,-15.017],[8.983,-24],[8.983,-24],[17.967,-15.017],[17.967,15.017],[8.983,24],[8.983,24],[0,15.017]],"c":true}],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-7.632,0],[0,0],[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632]],"o":[[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632],[0,0],[-7.632,0],[0,0]],"v":[[0,-10.171],[13.829,-24],[13.829,-24],[27.659,-10.171],[27.659,10.171],[13.829,24],[13.829,24],[0,10.171]],"c":true}],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-8.91,0],[0,0],[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91]],"o":[[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91],[0,0],[-8.91,0],[0,0]],"v":[[0,-7.856],[16.144,-24],[16.144,-24],[32.287,-7.856],[32.287,7.856],[16.144,24],[16.144,24],[0,7.856]],"c":true}],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-9.63,0],[0,0],[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63]],"o":[[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63],[0,0],[-9.63,0],[0,0]],"v":[[0,-6.551],[17.449,-24],[17.449,-24],[34.898,-6.551],[34.898,6.551],[17.449,24],[17.449,24],[0,6.551]],"c":true}],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.063,0],[0,0],[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063]],"o":[[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063],[0,0],[-10.063,0],[0,0]],"v":[[0,-5.766],[18.234,-24],[18.234,-24],[36.467,-5.766],[36.467,5.766],[18.234,24],[18.234,24],[0,5.766]],"c":true}],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.317,0],[0,0],[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317]],"o":[[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317],[0,0],[-10.317,0],[0,0]],"v":[[0,-5.306],[18.694,-24],[18.694,-24],[37.388,-5.306],[37.388,5.306],[18.694,24],[18.694,24],[0,5.306]],"c":true}],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.448,0],[0,0],[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448]],"o":[[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448],[0,0],[-10.448,0],[0,0]],"v":[[0,-5.07],[18.93,-24],[18.93,-24],[37.861,-5.07],[37.861,5.07],[18.93,24],[18.93,24],[0,5.07]],"c":true}],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.486,0],[0,0],[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486]],"o":[[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486],[0,0],[-10.486,0],[0,0]],"v":[[0,-5],[19,-24],[19,-24],[38,-5],[38,5],[19,24],[19,24],[0,5]],"c":true}],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-12.154,0],[0,0],[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154]],"o":[[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154],[0,0],[-12.154,0],[0,0]],"v":[[0,-1.977],[22.023,-24],[22.023,-24],[44.045,-1.977],[44.045,1.977],[22.023,24],[22.023,24],[0,1.977]],"c":true}],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.079,0],[0,0],[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079]],"o":[[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079],[0,0],[-13.079,0],[0,0]],"v":[[0,-0.302],[23.698,-24],[23.698,-24],[47.396,-0.302],[47.396,0.302],[23.698,24],[23.698,24],[0,0.302]],"c":true}],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[24,-24],[48,0],[48,0],[24,24],[24,24],[0,0]],"c":true}],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[24.149,-24],[48.149,0],[48.149,0],[24.149,24],[24,24],[0,0]],"c":true}],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[24.698,-24],[48.698,0],[48.698,0],[24.698,24],[24,24],[0,0]],"c":true}],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[25.714,-24],[49.714,0],[49.714,0],[25.714,24],[24,24],[0,0]],"c":true}],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[26.973,-24],[50.973,0],[50.973,0],[26.973,24],[24,24],[0,0]],"c":true}],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[28.19,-24],[52.19,0],[52.19,0],[28.19,24],[24,24],[0,0]],"c":true}],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[29.268,-24],[53.268,0],[53.268,0],[29.268,24],[24,24],[0,0]],"c":true}],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[30.206,-24],[54.206,0],[54.206,0],[30.206,24],[24,24],[0,0]],"c":true}],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[31.028,-24],[55.028,0],[55.028,0],[31.028,24],[24,24],[0,0]],"c":true}],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[31.755,-24],[55.755,0],[55.755,0],[31.755,24],[24,24],[0,0]],"c":true}],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[32.405,-24],[56.405,0],[56.405,0],[32.405,24],[24,24],[0,0]],"c":true}],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[32.99,-24],[56.99,0],[56.99,0],[32.99,24],[24,24],[0,0]],"c":true}],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[33.522,-24],[57.522,0],[57.522,0],[33.522,24],[24,24],[0,0]],"c":true}],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[34.006,-24],[58.006,0],[58.006,0],[34.006,24],[24,24],[0,0]],"c":true}],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[34.451,-24],[58.451,0],[58.451,0],[34.451,24],[24,24],[0,0]],"c":true}],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[34.861,-24],[58.861,0],[58.861,0],[34.861,24],[24,24],[0,0]],"c":true}],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[35.24,-24],[59.24,0],[59.24,0],[35.24,24],[24,24],[0,0]],"c":true}],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[35.591,-24],[59.591,0],[59.591,0],[35.591,24],[24,24],[0,0]],"c":true}],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[35.917,-24],[59.917,0],[59.917,0],[35.917,24],[24,24],[0,0]],"c":true}],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[36.221,-24],[60.221,0],[60.221,0],[36.221,24],[24,24],[0,0]],"c":true}],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[36.504,-24],[60.504,0],[60.504,0],[36.504,24],[24,24],[0,0]],"c":true}],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[36.769,-24],[60.769,0],[60.769,0],[36.769,24],[24,24],[0,0]],"c":true}],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.017,-24],[61.017,0],[61.017,0],[37.017,24],[24,24],[0,0]],"c":true}],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.248,-24],[61.248,0],[61.248,0],[37.248,24],[24,24],[0,0]],"c":true}],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.465,-24],[61.465,0],[61.465,0],[37.465,24],[24,24],[0,0]],"c":true}],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.669,-24],[61.669,0],[61.669,0],[37.669,24],[24,24],[0,0]],"c":true}],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.859,-24],[61.859,0],[61.859,0],[37.859,24],[24,24],[0,0]],"c":true}],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.038,-24],[62.038,0],[62.038,0],[38.038,24],[24,24],[0,0]],"c":true}],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.205,-24],[62.205,0],[62.205,0],[38.205,24],[24,24],[0,0]],"c":true}],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.362,-24],[62.362,0],[62.362,0],[38.362,24],[24,24],[0,0]],"c":true}],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.509,-24],[62.509,0],[62.509,0],[38.509,24],[24,24],[0,0]],"c":true}],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.647,-24],[62.647,0],[62.647,0],[38.647,24],[24,24],[0,0]],"c":true}],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.776,-24],[62.776,0],[62.776,0],[38.776,24],[24,24],[0,0]],"c":true}],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.896,-24],[62.896,0],[62.896,0],[38.896,24],[24,24],[0,0]],"c":true}],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.008,-24],[63.008,0],[63.008,0],[39.008,24],[24,24],[0,0]],"c":true}],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.113,-24],[63.113,0],[63.113,0],[39.113,24],[24,24],[0,0]],"c":true}],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.21,-24],[63.21,0],[63.21,0],[39.21,24],[24,24],[0,0]],"c":true}],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.3,-24],[63.3,0],[63.3,0],[39.3,24],[24,24],[0,0]],"c":true}],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.384,-24],[63.384,0],[63.384,0],[39.384,24],[24,24],[0,0]],"c":true}],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.461,-24],[63.461,0],[63.461,0],[39.461,24],[24,24],[0,0]],"c":true}],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.532,-24],[63.532,0],[63.532,0],[39.532,24],[24,24],[0,0]],"c":true}],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.597,-24],[63.597,0],[63.597,0],[39.597,24],[24,24],[0,0]],"c":true}],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.657,-24],[63.657,0],[63.657,0],[39.657,24],[24,24],[0,0]],"c":true}],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.711,-24],[63.711,0],[63.711,0],[39.711,24],[24,24],[0,0]],"c":true}],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.76,-24],[63.76,0],[63.76,0],[39.76,24],[24,24],[0,0]],"c":true}],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.804,-24],[63.804,0],[63.804,0],[39.804,24],[24,24],[0,0]],"c":true}],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.843,-24],[63.843,0],[63.843,0],[39.843,24],[24,24],[0,0]],"c":true}],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.877,-24],[63.877,0],[63.877,0],[39.877,24],[24,24],[0,0]],"c":true}],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.907,-24],[63.907,0],[63.907,0],[39.907,24],[24,24],[0,0]],"c":true}],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.932,-24],[63.932,0],[63.932,0],[39.932,24],[24,24],[0,0]],"c":true}],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.971,-24],[63.971,0],[63.971,0],[39.971,24],[24,24],[0,0]],"c":true}],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"k":[{"s":[0,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":450,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"IndieCorners Shape","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":249,"s":[100]},{"t":255,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,0,0],"t":123,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0,0],"t":124,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.001,0,0],"t":125,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.005,0,0],"t":126,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.013,0,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.029,0,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.054,0,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.089,0,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.134,0,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.193,0,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.267,0,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.358,0,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.466,0,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.593,0,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.739,0,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.903,0,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.054,0,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.22,0,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.403,0,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.602,0,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.821,0,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.059,0,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.319,0,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.601,0,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.909,0,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.242,0,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.604,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.998,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[4.427,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[4.897,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[5.407,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[5.965,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[6.576,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[7.246,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[7.983,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[8.8,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[9.701,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[10.699,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[11.808,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[13.041,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[14.414,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[15.945,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[17.621,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[19.429,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[21.324,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.241,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[25.111,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[26.859,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.457,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.897,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.185,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[32.333,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[33.36,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[34.272,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[35.088,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[35.82,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.479,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.076,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.613,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.099,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.538,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.937,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.299,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.629,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.927,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.198,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.442,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.663,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.862,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.041,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.2,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.342,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.467,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.577,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.672,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.754,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.822,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.879,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.925,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.961,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}]}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"right circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[-41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"left circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"size","bm":0,"hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"pb:scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"k":[{"s":[277.263,197.5,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.43,197.5,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.681,197.5,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.85,197.5,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.058,197.5,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.313,197.5,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.63,197.5,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.023,197.5,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.517,197.5,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.151,197.5,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.992,197.5,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.175,197.5,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.778,197.5,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.586,197.5,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[287.564,197.5,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[289.63,197.5,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[291.671,197.5,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[293.578,197.5,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[295.298,197.5,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[296.823,197.5,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[298.167,197.5,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[299.353,197.5,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[300.405,197.5,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[301.343,197.5,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.187,197.5,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.949,197.5,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[303.641,197.5,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.271,197.5,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.846,197.5,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.373,197.5,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.858,197.5,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.306,197.5,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.72,197.5,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.103,197.5,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.459,197.5,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.789,197.5,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.096,197.5,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.382,197.5,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.648,197.5,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.895,197.5,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.126,197.5,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.34,197.5,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.54,197.5,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.726,197.5,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.901,197.5,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.063,197.5,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.352,197.5,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.599,197.5,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.903,197.5,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.196,197.5,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.191,197.5,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.194,197.5,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.275,197.5,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.841,197.5,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[297.7,197.5,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[289.568,197.5,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.993,197.5,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.914,197.5,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.504,197.5,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[281.464,197.5,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.661,197.5,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.021,197.5,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.499,197.5,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.068,197.5,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.705,197.5,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.401,197.5,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.143,197.5,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.925,197.5,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.741,197.5,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.585,197.5,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.345,197.5,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.074,197.5,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"k":[{"s":[99.914,99.914,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.848,99.848,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.751,99.751,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.685,99.685,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.605,99.605,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.507,99.507,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.387,99.387,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.239,99.239,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.056,99.056,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.829,98.829,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.542,98.542,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.174,98.174,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.686,97.686,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97,97,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.071,96.071,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.025,95.025,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.878,93.878,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.678,92.678,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.495,91.495,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.39,90.39,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[89.393,89.393,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.508,88.508,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.729,87.729,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.041,87.041,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[86.43,86.43,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.886,85.886,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.397,85.397,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.956,84.956,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.555,84.555,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.191,84.191,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.857,83.857,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.552,83.552,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.271,83.271,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.011,83.011,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.771,82.771,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.549,82.549,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.342,82.342,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.151,82.151,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.973,81.973,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.807,81.807,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.653,81.653,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.51,81.51,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.376,81.376,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.251,81.251,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.135,81.135,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.027,81.027,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.926,80.926,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.833,80.833,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.746,80.746,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.665,80.665,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.591,80.591,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.522,80.522,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.458,80.458,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.4,80.4,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.346,80.346,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.298,80.298,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.253,80.253,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.176,80.176,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.115,80.115,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.049,80.049,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.179,80.179,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.757,80.757,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.87,81.87,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.86,83.86,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88,88,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.714,92.714,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.789,94.789,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.992,95.992,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.809,96.809,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.412,97.412,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.878,97.878,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.249,98.249,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.553,98.553,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.803,98.803,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.012,99.012,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.188,99.188,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.337,99.337,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.464,99.464,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.661,99.661,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.737,99.737,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.8,99.8,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.896,99.896,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.99,99.99,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":256,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":7}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277.263,197.5],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.43,197.5],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.681,197.5],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.85,197.5],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.058,197.5],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.313,197.5],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.63,197.5],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.023,197.5],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.517,197.5],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.151,197.5],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.992,197.5],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.175,197.5],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.778,197.5],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.586,197.5],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[287.564,197.5],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[289.63,197.5],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[291.671,197.5],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[293.578,197.5],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[295.298,197.5],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[296.823,197.5],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[298.167,197.5],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[299.353,197.5],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[300.405,197.5],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[301.343,197.5],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.187,197.5],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.949,197.5],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[303.641,197.5],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.271,197.5],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.846,197.5],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.373,197.5],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.858,197.5],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.306,197.5],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.72,197.5],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.103,197.5],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.459,197.5],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.789,197.5],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.096,197.5],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.382,197.5],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.648,197.5],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.895,197.5],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.126,197.5],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.34,197.5],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.54,197.5],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.726,197.5],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.901,197.5],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.063,197.5],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.352,197.5],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.599,197.5],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.903,197.5],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.196,197.5],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.5,197.5],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.936,197.788],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.7,199.014],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.071,202.033],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[298.438,206.77],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[293.978,211.581],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[290.807,215.785],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[288.487,219.444],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[286.718,222.659],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.317,225.519],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[284.171,228.085],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.211,230.396],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.392,232.474],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[281.682,234.334],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[281.059,235.992],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.506,237.461],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.012,238.754],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.568,239.881],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.169,240.855],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.809,241.684],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.487,242.379],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.199,242.951],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.943,243.409],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.72,243.76],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.528,243.874],"t":274,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.369,243.701],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.24,243.336],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.142,242.847],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.073,242.284],"t":278,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.033,241.684],"t":279,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,241.075],"t":280,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,240.497],"t":281,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.98],"t":282,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.538],"t":283,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.181],"t":284,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,238.917],"t":285,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.065],"t":293,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.265],"t":295,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.455],"t":297,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.685],"t":300,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.85,239.729],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.285,239.199],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.162,238.218],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.05,236.594],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[267.986,234.04],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[265.592,226.983],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.166,217.207],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[272.184,212.309],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.238,209.328],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.904,207.237],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.379,205.654],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.741,204.399],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.029,203.375],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.267,202.521],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.466,201.799],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.638,201.182],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.788,200.65],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.921,200.189],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.041,199.789],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.149,199.439],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.246,199.134],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.337,198.867],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.419,198.634],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.495,198.431],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.566,198.255],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.632,198.103],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.692,197.973],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.748,197.862],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.86,197.692],"t":410,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":280,"s":[30,30]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":280,"s":[30]},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450982481,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"matte","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":250,"s":[0,0,0],"to":[-28.906,14.531,0],"ti":[7.183,-8.833,0]},{"t":280,"s":[-43.1,53,0],"h":1},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":380,"s":[-43.1,53,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":386,"s":[-25.86,31.8,0],"to":[7.183,-8.833,0],"ti":[-7.167,9.833,0]},{"t":416,"s":[0,0,0]}]},"a":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":255,"s":[0,0,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.573,"y":1},"o":{"x":0.236,"y":0},"t":273,"s":[0,-6,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":287,"s":[0,1.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":307,"s":[0,0,0]}]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":280,"s":[30,30]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":280,"s":[30]},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"Back_LofiApp","parent":9,"tt":1,"tp":9,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":280,"s":[10,10,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[10,10,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[46,46,100]},{"t":416,"s":[100,100,100]}]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"behindApp","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":259,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":386,"s":[0]},{"t":397,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[503.5,314.5]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"Back_LofiLauncher","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":25,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":450,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Back_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[339.937,151.75,0]},"a":{"a":0,"k":[339.937,151.75,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[334,279]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[334,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":16},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,171.125,0]},"a":{"a":0,"k":[82,171.125,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 2","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,140,0]},"a":{"a":0,"k":[82,140.938,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Search","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"header","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,171]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"block","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app only","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Back_LofiLauncher","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,117.5,0]},"a":{"a":0,"k":[252,275,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[300,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[168,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":15},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[132,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[20.144,20.144],[20.144,-20.144],[0,0],[-20.144,-20.144],[-20.144,20.144],[0,0]],"o":[[-20.144,-20.144],[0,0],[-20.144,20.144],[20.144,20.144],[0,0],[20.144,-20.144]],"v":[[44.892,-44.892],[-28.057,-44.892],[-44.892,-28.057],[-44.892,44.892],[28.057,44.892],[44.892,28.057]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":15},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[108,152.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets weather","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.782,-2.684],[0,0],[2.63,-0.033],[0,0],[2.807,-4.716],[0,0],[2.263,-1.343],[0,0],[0.066,-5.485],[0,0],[1.292,-2.295],[0,0],[-2.683,-4.784],[0,0],[-0.033,-2.63],[0,0],[-4.716,-2.807],[0,0],[-1.338,-2.263],[0,0],[-5.483,-0.066],[0,0],[-2.296,-1.292],[0,0],[-4.782,2.683],[0,0],[-2.63,0.033],[0,0],[-2.807,4.716],[0,0],[-2.263,1.338],[0,0],[-0.066,5.483],[0,0],[-1.292,2.295],[0,0],[2.683,4.784],[0,0],[0.033,2.631],[0,0],[4.716,2.801],[0,0],[1.338,2.262],[0,0],[5.483,0.068],[0,0],[2.296,1.287]],"o":[[-4.782,-2.684],[0,0],[-2.296,1.287],[0,0],[-5.483,0.068],[0,0],[-1.338,2.262],[0,0],[-4.716,2.801],[0,0],[-0.033,2.631],[0,0],[-2.683,4.784],[0,0],[1.292,2.295],[0,0],[0.066,5.483],[0,0],[2.263,1.338],[0,0],[2.807,4.716],[0,0],[2.63,0.033],[0,0],[4.782,2.683],[0,0],[2.296,-1.292],[0,0],[5.483,-0.066],[0,0],[1.338,-2.263],[0,0],[4.716,-2.807],[0,0],[0.033,-2.63],[0,0],[2.683,-4.784],[0,0],[-1.292,-2.295],[0,0],[-0.066,-5.485],[0,0],[-2.263,-1.343],[0,0],[-2.807,-4.716],[0,0],[-2.63,-0.033],[0,0]],"v":[[7.7,-57.989],[-7.7,-57.989],[-11.019,-56.128],[-18.523,-54.117],[-22.327,-54.07],[-35.668,-46.369],[-37.609,-43.1],[-43.099,-37.605],[-46.372,-35.663],[-54.072,-22.324],[-54.118,-18.522],[-56.132,-11.016],[-57.988,-7.7],[-57.988,7.703],[-56.132,11.019],[-54.118,18.524],[-54.072,22.328],[-46.372,35.669],[-43.099,37.611],[-37.609,43.101],[-35.668,46.373],[-22.327,54.074],[-18.523,54.12],[-11.019,56.133],[-7.7,57.99],[7.7,57.99],[11.019,56.133],[18.523,54.12],[22.327,54.074],[35.668,46.373],[37.609,43.101],[43.099,37.611],[46.372,35.669],[54.072,22.328],[54.118,18.524],[56.132,11.019],[57.988,7.703],[57.988,-7.7],[56.132,-11.016],[54.118,-18.522],[54.072,-22.324],[46.372,-35.663],[43.099,-37.605],[37.609,-43.1],[35.668,-46.369],[22.327,-54.07],[18.523,-54.117],[11.019,-56.128]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":15},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,104.003]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets clock","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 7","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,128.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,56.002]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[156,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[60,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Scale Up","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":250,"s":[85,85,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":256,"s":[91,91,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":286,"s":[100,100,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[96,96,100]},{"t":416,"s":[90,90,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"Back_RightDismiss","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"release Scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":0,"k":476},"y":{"a":0,"k":197}},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.08,0.08,0.08],"y":[0.47,0.47,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.999,0.999,0.999],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":254,"s":[105,105,100]},{"t":266,"s":[50,50,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":151,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":255,"s":[100]},{"t":258,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[-0.692,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.692,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.392,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.675,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.521,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-16.835,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-18.141,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-18.925,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.386,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.622,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.692,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-22.714,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-24.39,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-24.692,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-24.766,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.041,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.549,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-26.178,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-26.787,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-27.326,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-27.795,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.206,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.57,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.894,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.187,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.453,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.695,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.917,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.122,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.312,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.487,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.65,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.802,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.944,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.076,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.2,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.316,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.424,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.526,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.621,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.711,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.795,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.873,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.947,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.015,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.08,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.14,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.196,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.248,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.297,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.342,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.384,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.422,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.458,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.49,0,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.52,0,0],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.547,0,0],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.572,0,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.594,0,0],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.613,0,0],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.645,0,0],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.677,0,0],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.36,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[5.459,5.2],[-3.459,0],[5.459,-5.2]],"c":false}]},{"i":{"x":0.02,"y":1},"o":{"x":0.167,"y":0.167},"t":152,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.779,4.88],[-3.459,0],[4.779,-4.88]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":159,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.12,"y":0},"t":162,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"t":217,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,9.2],[-3.459,0],[3.459,-9.2]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.121568627656,0.211764708161,0.101960785687,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":4},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Vector 1","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":257,"s":[100]},{"t":260,"s":[0]}]},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.06],"y":[-0.15]},"t":160,"s":[13.981]},{"t":189,"s":[-0.019]}]},"y":{"a":0,"k":0}},"a":{"a":0,"k":[-31.019,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"IndieCorners","np":21,"mn":"Pseudo/0.20784385308943532","ix":1,"en":1,"ef":[{"ty":7,"nm":"Align","mn":"Pseudo/0.20784385308943532-0001","ix":1,"v":{"a":0,"k":4}},{"ty":6,"nm":"Size","mn":"Pseudo/0.20784385308943532-0002","ix":2,"v":0},{"ty":0,"nm":"w","mn":"Pseudo/0.20784385308943532-0003","ix":3,"v":{"a":1,"k":[{"t":149,"s":[0],"h":1},{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[8]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[38]},{"i":{"x":[0.002],"y":[1]},"o":{"x":[0.119],"y":[0]},"t":162,"s":[48]},{"t":217,"s":[64]}]}},{"ty":0,"nm":"h","mn":"Pseudo/0.20784385308943532-0004","ix":4,"v":{"a":0,"k":48}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0005","ix":5,"v":0},{"ty":6,"nm":"Rounding","mn":"Pseudo/0.20784385308943532-0006","ix":6,"v":0},{"ty":7,"nm":"Same for all corners","mn":"Pseudo/0.20784385308943532-0007","ix":7,"v":{"a":0,"k":1}},{"ty":0,"nm":"All corners","mn":"Pseudo/0.20784385308943532-0008","ix":8,"v":{"a":1,"k":[{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[80]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[24]},{"t":162,"s":[80]}]}},{"ty":0,"nm":"tl","mn":"Pseudo/0.20784385308943532-0009","ix":9,"v":{"a":0,"k":12}},{"ty":0,"nm":"tr","mn":"Pseudo/0.20784385308943532-0010","ix":10,"v":{"a":0,"k":12}},{"ty":0,"nm":"br","mn":"Pseudo/0.20784385308943532-0011","ix":11,"v":{"a":0,"k":12}},{"ty":0,"nm":"bl","mn":"Pseudo/0.20784385308943532-0012","ix":12,"v":{"a":0,"k":12}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0013","ix":13,"v":0},{"ty":6,"nm":"Alignment","mn":"Pseudo/0.20784385308943532-0014","ix":14,"v":0},{"ty":0,"nm":"X Anchor %","mn":"Pseudo/0.20784385308943532-0015","ix":15,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Anchor %","mn":"Pseudo/0.20784385308943532-0016","ix":16,"v":{"a":0,"k":0}},{"ty":0,"nm":"X Position","mn":"Pseudo/0.20784385308943532-0017","ix":17,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Position ","mn":"Pseudo/0.20784385308943532-0018","ix":18,"v":{"a":0,"k":0}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0019","ix":19,"v":0}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"k":[{"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[0,-24],[0,-24],[0,-24],[0,-24],[0,24],[0,24],[0,24],[0,24]],"c":true}],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.208,0],[0,0],[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208]],"o":[[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208],[0,0],[-2.208,0],[0,0]],"v":[[-8,-20],[-4,-24],[-4,-24],[0,-20],[0,20],[-4,24],[-4,24],[-8,20]],"c":true}],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.594,0],[0,0],[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594]],"o":[[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594],[0,0],[-2.594,0],[0,0]],"v":[[-9.401,-19.3],[-4.7,-24],[-4.7,-24],[0,-19.3],[0,19.3],[-4.7,24],[-4.7,24],[-9.401,19.3]],"c":true}],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-4.958,0],[0,0],[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958]],"o":[[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958],[0,0],[-4.958,0],[0,0]],"v":[[-17.967,-15.017],[-8.983,-24],[-8.983,-24],[0,-15.017],[0,15.017],[-8.983,24],[-8.983,24],[-17.967,15.017]],"c":true}],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-7.632,0],[0,0],[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632]],"o":[[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632],[0,0],[-7.632,0],[0,0]],"v":[[-27.659,-10.171],[-13.829,-24],[-13.829,-24],[0,-10.171],[0,10.171],[-13.829,24],[-13.829,24],[-27.659,10.171]],"c":true}],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-8.91,0],[0,0],[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91]],"o":[[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91],[0,0],[-8.91,0],[0,0]],"v":[[-32.287,-7.856],[-16.144,-24],[-16.144,-24],[0,-7.856],[0,7.856],[-16.144,24],[-16.144,24],[-32.287,7.856]],"c":true}],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-9.63,0],[0,0],[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63]],"o":[[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63],[0,0],[-9.63,0],[0,0]],"v":[[-34.898,-6.551],[-17.449,-24],[-17.449,-24],[0,-6.551],[0,6.551],[-17.449,24],[-17.449,24],[-34.898,6.551]],"c":true}],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.063,0],[0,0],[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063]],"o":[[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063],[0,0],[-10.063,0],[0,0]],"v":[[-36.467,-5.766],[-18.234,-24],[-18.234,-24],[0,-5.766],[0,5.766],[-18.234,24],[-18.234,24],[-36.467,5.766]],"c":true}],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.317,0],[0,0],[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317]],"o":[[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317],[0,0],[-10.317,0],[0,0]],"v":[[-37.388,-5.306],[-18.694,-24],[-18.694,-24],[0,-5.306],[0,5.306],[-18.694,24],[-18.694,24],[-37.388,5.306]],"c":true}],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.448,0],[0,0],[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448]],"o":[[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448],[0,0],[-10.448,0],[0,0]],"v":[[-37.861,-5.07],[-18.93,-24],[-18.93,-24],[0,-5.07],[0,5.07],[-18.93,24],[-18.93,24],[-37.861,5.07]],"c":true}],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.486,0],[0,0],[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486]],"o":[[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486],[0,0],[-10.486,0],[0,0]],"v":[[-38,-5],[-19,-24],[-19,-24],[0,-5],[0,5],[-19,24],[-19,24],[-38,5]],"c":true}],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-12.154,0],[0,0],[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154]],"o":[[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154],[0,0],[-12.154,0],[0,0]],"v":[[-44.045,-1.977],[-22.023,-24],[-22.023,-24],[0,-1.977],[0,1.977],[-22.023,24],[-22.023,24],[-44.045,1.977]],"c":true}],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.079,0],[0,0],[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079]],"o":[[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079],[0,0],[-13.079,0],[0,0]],"v":[[-47.396,-0.302],[-23.698,-24],[-23.698,-24],[0,-0.302],[0,0.302],[-23.698,24],[-23.698,24],[-47.396,0.302]],"c":true}],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-48,0],[-24,-24],[-24,-24],[0,0],[0,0],[-24,24],[-24,24],[-48,0]],"c":true}],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-48.149,0],[-24.149,-24],[-24,-24],[0,0],[0,0],[-24,24],[-24.149,24],[-48.149,0]],"c":true}],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-48.698,0],[-24.698,-24],[-24,-24],[0,0],[0,0],[-24,24],[-24.698,24],[-48.698,0]],"c":true}],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-49.714,0],[-25.714,-24],[-24,-24],[0,0],[0,0],[-24,24],[-25.714,24],[-49.714,0]],"c":true}],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-50.973,0],[-26.973,-24],[-24,-24],[0,0],[0,0],[-24,24],[-26.973,24],[-50.973,0]],"c":true}],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-52.19,0],[-28.19,-24],[-24,-24],[0,0],[0,0],[-24,24],[-28.19,24],[-52.19,0]],"c":true}],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-53.268,0],[-29.268,-24],[-24,-24],[0,0],[0,0],[-24,24],[-29.268,24],[-53.268,0]],"c":true}],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-54.206,0],[-30.206,-24],[-24,-24],[0,0],[0,0],[-24,24],[-30.206,24],[-54.206,0]],"c":true}],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-55.028,0],[-31.028,-24],[-24,-24],[0,0],[0,0],[-24,24],[-31.028,24],[-55.028,0]],"c":true}],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-55.755,0],[-31.755,-24],[-24,-24],[0,0],[0,0],[-24,24],[-31.755,24],[-55.755,0]],"c":true}],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-56.405,0],[-32.405,-24],[-24,-24],[0,0],[0,0],[-24,24],[-32.405,24],[-56.405,0]],"c":true}],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-56.99,0],[-32.99,-24],[-24,-24],[0,0],[0,0],[-24,24],[-32.99,24],[-56.99,0]],"c":true}],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-57.522,0],[-33.522,-24],[-24,-24],[0,0],[0,0],[-24,24],[-33.522,24],[-57.522,0]],"c":true}],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-58.006,0],[-34.006,-24],[-24,-24],[0,0],[0,0],[-24,24],[-34.006,24],[-58.006,0]],"c":true}],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-58.451,0],[-34.451,-24],[-24,-24],[0,0],[0,0],[-24,24],[-34.451,24],[-58.451,0]],"c":true}],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-58.861,0],[-34.861,-24],[-24,-24],[0,0],[0,0],[-24,24],[-34.861,24],[-58.861,0]],"c":true}],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-59.24,0],[-35.24,-24],[-24,-24],[0,0],[0,0],[-24,24],[-35.24,24],[-59.24,0]],"c":true}],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-59.591,0],[-35.591,-24],[-24,-24],[0,0],[0,0],[-24,24],[-35.591,24],[-59.591,0]],"c":true}],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-59.917,0],[-35.917,-24],[-24,-24],[0,0],[0,0],[-24,24],[-35.917,24],[-59.917,0]],"c":true}],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-60.221,0],[-36.221,-24],[-24,-24],[0,0],[0,0],[-24,24],[-36.221,24],[-60.221,0]],"c":true}],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-60.504,0],[-36.504,-24],[-24,-24],[0,0],[0,0],[-24,24],[-36.504,24],[-60.504,0]],"c":true}],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-60.769,0],[-36.769,-24],[-24,-24],[0,0],[0,0],[-24,24],[-36.769,24],[-60.769,0]],"c":true}],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.017,0],[-37.017,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.017,24],[-61.017,0]],"c":true}],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.248,0],[-37.248,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.248,24],[-61.248,0]],"c":true}],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.465,0],[-37.465,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.465,24],[-61.465,0]],"c":true}],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.669,0],[-37.669,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.669,24],[-61.669,0]],"c":true}],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.859,0],[-37.859,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.859,24],[-61.859,0]],"c":true}],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.038,0],[-38.038,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.038,24],[-62.038,0]],"c":true}],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.205,0],[-38.205,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.205,24],[-62.205,0]],"c":true}],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.362,0],[-38.362,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.362,24],[-62.362,0]],"c":true}],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.509,0],[-38.509,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.509,24],[-62.509,0]],"c":true}],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.647,0],[-38.647,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.647,24],[-62.647,0]],"c":true}],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.776,0],[-38.776,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.776,24],[-62.776,0]],"c":true}],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.896,0],[-38.896,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.896,24],[-62.896,0]],"c":true}],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.008,0],[-39.008,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.008,24],[-63.008,0]],"c":true}],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.113,0],[-39.113,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.113,24],[-63.113,0]],"c":true}],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.21,0],[-39.21,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.21,24],[-63.21,0]],"c":true}],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.3,0],[-39.3,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.3,24],[-63.3,0]],"c":true}],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.384,0],[-39.384,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.384,24],[-63.384,0]],"c":true}],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.461,0],[-39.461,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.461,24],[-63.461,0]],"c":true}],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.532,0],[-39.532,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.532,24],[-63.532,0]],"c":true}],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.597,0],[-39.597,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.597,24],[-63.597,0]],"c":true}],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.657,0],[-39.657,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.657,24],[-63.657,0]],"c":true}],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.711,0],[-39.711,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.711,24],[-63.711,0]],"c":true}],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.76,0],[-39.76,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.76,24],[-63.76,0]],"c":true}],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.804,0],[-39.804,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.804,24],[-63.804,0]],"c":true}],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.843,0],[-39.843,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.843,24],[-63.843,0]],"c":true}],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.877,0],[-39.877,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.877,24],[-63.877,0]],"c":true}],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.907,0],[-39.907,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.907,24],[-63.907,0]],"c":true}],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.932,0],[-39.932,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.932,24],[-63.932,0]],"c":true}],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.971,0],[-39.971,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.971,24],[-63.971,0]],"c":true}],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"k":[{"s":[0,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":498,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"IndieCorners Shape","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":249,"s":[100]},{"t":255,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,0,0],"t":123,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0,0],"t":124,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.001,0,0],"t":125,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.005,0,0],"t":126,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.013,0,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.029,0,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.054,0,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.089,0,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.134,0,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.193,0,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.267,0,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.358,0,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.466,0,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.593,0,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.739,0,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.903,0,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.054,0,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.22,0,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.403,0,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.602,0,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.821,0,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.059,0,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.319,0,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.601,0,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.909,0,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.242,0,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.604,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.998,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.427,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.897,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.407,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.965,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-6.576,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.246,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.983,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-8.8,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.701,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.699,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.808,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.041,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.414,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-15.945,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-17.621,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.429,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-21.324,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-23.241,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.111,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-26.859,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.457,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.897,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.185,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.333,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-33.36,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-34.272,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-35.088,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-35.82,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.479,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-37.076,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-37.613,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.099,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.538,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.937,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.299,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.629,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.927,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.198,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.442,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.663,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.862,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.041,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.2,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.342,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.467,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.577,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.672,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.754,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.822,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.879,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.925,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.961,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}]}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"right circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[-41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"left circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"size","bm":0,"hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"pb:scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"k":[{"s":[276.737,197.5,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.57,197.5,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.319,197.5,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.15,197.5,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.942,197.5,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.687,197.5,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.37,197.5,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.978,197.5,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.484,197.5,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.85,197.5,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.008,197.5,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.825,197.5,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.222,197.5,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[268.416,197.5,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[266.436,197.5,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[264.37,197.5,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[262.33,197.5,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[260.423,197.5,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[258.703,197.5,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[257.178,197.5,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[255.833,197.5,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[254.646,197.5,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[253.594,197.5,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252.657,197.5,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.814,197.5,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.052,197.5,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[250.36,197.5,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.73,197.5,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.154,197.5,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.627,197.5,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.142,197.5,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.694,197.5,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.28,197.5,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.897,197.5,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.541,197.5,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.211,197.5,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.904,197.5,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.619,197.5,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.353,197.5,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.107,197.5,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.876,197.5,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.661,197.5,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.461,197.5,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.274,197.5,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.099,197.5,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.937,197.5,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.787,197.5,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.648,197.5,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.52,197.5,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.291,197.5,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.098,197.5,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.866,197.5,0],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.655,197.5,0],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.5,197.5,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.809,197.5,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.805,197.5,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.726,197.5,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.159,197.5,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[256.3,197.5,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[264.431,197.5,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[268.009,197.5,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.087,197.5,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.496,197.5,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[272.536,197.5,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.339,197.5,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.98,197.5,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.502,197.5,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.933,197.5,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.295,197.5,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.599,197.5,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.857,197.5,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.075,197.5,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.259,197.5,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.415,197.5,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.655,197.5,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.926,197.5,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"k":[{"s":[99.914,99.914,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.848,99.848,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.751,99.751,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.685,99.685,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.605,99.605,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.507,99.507,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.387,99.387,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.239,99.239,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.056,99.056,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.829,98.829,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.542,98.542,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.174,98.174,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.686,97.686,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97,97,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.071,96.071,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.025,95.025,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.878,93.878,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.678,92.678,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.495,91.495,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.39,90.39,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[89.393,89.393,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.508,88.508,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.729,87.729,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.041,87.041,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[86.43,86.43,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.886,85.886,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.397,85.397,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.956,84.956,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.555,84.555,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.191,84.191,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.857,83.857,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.552,83.552,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.271,83.271,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.011,83.011,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.771,82.771,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.549,82.549,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.342,82.342,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.151,82.151,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.973,81.973,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.807,81.807,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.653,81.653,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.51,81.51,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.376,81.376,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.251,81.251,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.135,81.135,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.027,81.027,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.926,80.926,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.833,80.833,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.746,80.746,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.665,80.665,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.591,80.591,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.522,80.522,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.458,80.458,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.4,80.4,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.346,80.346,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.298,80.298,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.253,80.253,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.176,80.176,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.115,80.115,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.049,80.049,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.179,80.179,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.757,80.757,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.87,81.87,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.86,83.86,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88,88,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.714,92.714,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.789,94.789,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.992,95.992,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.809,96.809,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.412,97.412,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.878,97.878,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.249,98.249,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.553,98.553,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.803,98.803,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.012,99.012,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.188,99.188,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.337,99.337,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.464,99.464,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.661,99.661,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.737,99.737,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.8,99.8,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.896,99.896,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.99,99.99,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":256,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":7}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[276.737,197.5],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.57,197.5],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.319,197.5],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.15,197.5],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.942,197.5],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.687,197.5],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.37,197.5],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.978,197.5],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.484,197.5],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.85,197.5],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.008,197.5],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.825,197.5],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.222,197.5],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[268.416,197.5],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[266.436,197.5],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[264.37,197.5],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[262.33,197.5],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[260.423,197.5],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[258.703,197.5],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[257.178,197.5],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[255.833,197.5],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[254.646,197.5],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[253.594,197.5],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252.657,197.5],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.814,197.5],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.052,197.5],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[250.36,197.5],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.73,197.5],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.154,197.5],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.627,197.5],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.142,197.5],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.694,197.5],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.28,197.5],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.897,197.5],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.541,197.5],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.211,197.5],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.904,197.5],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.619,197.5],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.353,197.5],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.107,197.5],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.876,197.5],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.661,197.5],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.461,197.5],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.274,197.5],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.099,197.5],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.937,197.5],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.787,197.5],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.648,197.5],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.52,197.5],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.291,197.5],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.098,197.5],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.866,197.5],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.655,197.5],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.5,197.5],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.159,197.525],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.792,197.842],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.233,199.672],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[257.542,204.005],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[262.216,208.989],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[265.399,213.457],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[267.667,217.355],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[269.364,220.773],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.685,223.812],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.743,226.538],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[272.606,228.991],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.321,231.197],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.92,233.179],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.427,234.953],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.858,236.532],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.225,237.926],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.539,239.152],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.808,240.219],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.038,241.138],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.233,241.918],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.398,242.568],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.537,243.099],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.653,243.517],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.824,243.574],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.884,243.254],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.928,242.802],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.959,242.269],"t":278,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.976,241.685],"t":279,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,241.075],"t":280,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,240.497],"t":281,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.98],"t":282,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.538],"t":283,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.181],"t":284,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,238.917],"t":285,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.065],"t":293,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.265],"t":295,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.455],"t":297,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.685],"t":300,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.15,239.729],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.715,239.199],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.839,238.218],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.95,236.594],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[286.015,234.04],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[288.407,226.983],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.954,217.108],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.005,212.156],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.975,209.156],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.314,207.064],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.834,205.487],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.461,204.24],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.159,203.226],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.905,202.385],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.691,201.676],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.503,201.072],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.339,200.553],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.193,200.105],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.061,199.716],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.941,199.376],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.831,199.079],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.729,198.82],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.636,198.594],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.551,198.398],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.472,198.228],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.399,198.082],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.333,197.956],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.209,197.759],"t":409,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.063,197.577],"t":412,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"t":280,"s":[30,30],"h":1},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"t":280,"s":[30],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450982481,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"matte","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":250,"s":[0,0,0],"to":[29.688,0.625,0],"ti":[0,0,0]},{"t":280,"s":[43.1,53,0],"h":1},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":380,"s":[43.1,53,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":386,"s":[25.86,31.8,0],"to":[0,0,0],"ti":[0,0,0]},{"t":416,"s":[0,0,0]}]},"a":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":255,"s":[0,0,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.573,"y":1},"o":{"x":0.236,"y":0},"t":273,"s":[0,-6,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":287,"s":[0,1.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":307,"s":[0,0,0]}]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"t":280,"s":[30,30],"h":1},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"t":280,"s":[30],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"Back_LofiApp","parent":9,"tt":1,"tp":9,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":280,"s":[10,10,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[10,10,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[46,46,100]},{"t":416,"s":[100,100,100]}]}},"ao":0,"w":504,"h":315,"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"behindApp","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":259,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":386,"s":[0]},{"t":397,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[503.5,314.5]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"Back_LofiLauncher","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":25,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":498,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Back_LeftDismiss","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[277,282,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":554,"h":564,"ip":0,"op":426,"st":-25,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Back_RightDismiss","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[277,282,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":554,"h":564,"ip":426,"op":902,"st":401,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/trackpad_back_success.json b/packages/SystemUI/res/raw/trackpad_back_success.json
new file mode 100644
index 0000000..56b6ff1
--- /dev/null
+++ b/packages/SystemUI/res/raw/trackpad_back_success.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":50,"w":554,"h":564,"nm":"Trackpad-JSON_Success","ddd":0,"assets":[{"id":"comp_0","nm":"TrackpadBack_Success_Checkmark","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Check Rotate","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":2,"s":[-16]},{"t":20,"s":[6]}]},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[95.049,95.049,100]}},"ao":0,"ip":0,"op":228,"st":-72,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Bounce","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":12,"s":[0]},{"t":36,"s":[-6]}]},"p":{"a":0,"k":[81,127,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.263,0.263,0.833],"y":[1.126,1.126,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.958,0.958,0]},"t":1,"s":[80,80,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.45,0.45,0.167],"y":[0.325,0.325,0]},"t":20,"s":[105,105,100]},{"t":36,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":-0.289},"p":{"a":0,"k":[14.364,-33.591,0]},"a":{"a":0,"k":[-0.125,0,0]},"s":{"a":0,"k":[104.744,104.744,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-1.401,-0.007],[-10.033,11.235]],"o":[[5.954,7.288],[1.401,0.007],[0,0]],"v":[[-28.591,4.149],[-10.73,26.013],[31.482,-21.255]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":3,"s":[0]},{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.001],"y":[0.149]},"t":10,"s":[29]},{"t":27,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":11},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":5,"op":44,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[95,95,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.275,0.275,0.21],"y":[1.102,1.102,1]},"o":{"x":[0.037,0.037,0.05],"y":[0.476,0.476,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.252,0.252,0.47],"y":[0.159,0.159,0]},"t":16,"s":[120,120,100]},{"t":28,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.32,0.32],"y":[0.11,0.11]},"t":16,"s":[148,148]},{"t":28,"s":[136,136]}]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":88},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"k":[{"s":[0.208,0.302,0.184,1],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.208,0.302,0.184,1],"t":43,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Checkbox - Widget","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Back_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[339.937,151.75,0]},"a":{"a":0,"k":[339.937,151.75,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[334,279]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[334,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":16},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,171.125,0]},"a":{"a":0,"k":[82,171.125,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 2","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,140,0]},"a":{"a":0,"k":[82,140.938,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Search","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"header","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,171]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"block","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app only","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Back_LofiLauncher","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,117.5,0]},"a":{"a":0,"k":[252,275,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":25,"nm":"Drop Shadow","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 2","np":8,"mn":"ADBE Drop Shadow","ix":2,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 3","np":8,"mn":"ADBE Drop Shadow","ix":3,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 4","np":8,"mn":"ADBE Drop Shadow","ix":4,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 5","np":8,"mn":"ADBE Drop Shadow","ix":5,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 6","np":8,"mn":"ADBE Drop Shadow","ix":6,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 7","np":8,"mn":"ADBE Drop Shadow","ix":7,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 8","np":8,"mn":"ADBE Drop Shadow","ix":8,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 9","np":8,"mn":"ADBE Drop Shadow","ix":9,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 10","np":8,"mn":"ADBE Drop Shadow","ix":10,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 11","np":8,"mn":"ADBE Drop Shadow","ix":11,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 12","np":8,"mn":"ADBE Drop Shadow","ix":12,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]}],"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[300,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[168,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":15},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[132,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":25,"nm":"Drop Shadow","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 2","np":8,"mn":"ADBE Drop Shadow","ix":2,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 3","np":8,"mn":"ADBE Drop Shadow","ix":3,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 4","np":8,"mn":"ADBE Drop Shadow","ix":4,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]}],"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[20.144,20.144],[20.144,-20.144],[0,0],[-20.144,-20.144],[-20.144,20.144],[0,0]],"o":[[-20.144,-20.144],[0,0],[-20.144,20.144],[20.144,20.144],[0,0],[20.144,-20.144]],"v":[[44.892,-44.892],[-28.057,-44.892],[-44.892,-28.057],[-44.892,44.892],[28.057,44.892],[44.892,28.057]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":15},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[108,152.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets weather","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.782,-2.684],[0,0],[2.63,-0.033],[0,0],[2.807,-4.716],[0,0],[2.263,-1.343],[0,0],[0.066,-5.485],[0,0],[1.292,-2.295],[0,0],[-2.683,-4.784],[0,0],[-0.033,-2.63],[0,0],[-4.716,-2.807],[0,0],[-1.338,-2.263],[0,0],[-5.483,-0.066],[0,0],[-2.296,-1.292],[0,0],[-4.782,2.683],[0,0],[-2.63,0.033],[0,0],[-2.807,4.716],[0,0],[-2.263,1.338],[0,0],[-0.066,5.483],[0,0],[-1.292,2.295],[0,0],[2.683,4.784],[0,0],[0.033,2.631],[0,0],[4.716,2.801],[0,0],[1.338,2.262],[0,0],[5.483,0.068],[0,0],[2.296,1.287]],"o":[[-4.782,-2.684],[0,0],[-2.296,1.287],[0,0],[-5.483,0.068],[0,0],[-1.338,2.262],[0,0],[-4.716,2.801],[0,0],[-0.033,2.631],[0,0],[-2.683,4.784],[0,0],[1.292,2.295],[0,0],[0.066,5.483],[0,0],[2.263,1.338],[0,0],[2.807,4.716],[0,0],[2.63,0.033],[0,0],[4.782,2.683],[0,0],[2.296,-1.292],[0,0],[5.483,-0.066],[0,0],[1.338,-2.263],[0,0],[4.716,-2.807],[0,0],[0.033,-2.63],[0,0],[2.683,-4.784],[0,0],[-1.292,-2.295],[0,0],[-0.066,-5.485],[0,0],[-2.263,-1.343],[0,0],[-2.807,-4.716],[0,0],[-2.63,-0.033],[0,0]],"v":[[7.7,-57.989],[-7.7,-57.989],[-11.019,-56.128],[-18.523,-54.117],[-22.327,-54.07],[-35.668,-46.369],[-37.609,-43.1],[-43.099,-37.605],[-46.372,-35.663],[-54.072,-22.324],[-54.118,-18.522],[-56.132,-11.016],[-57.988,-7.7],[-57.988,7.703],[-56.132,11.019],[-54.118,18.524],[-54.072,22.328],[-46.372,35.669],[-43.099,37.611],[-37.609,43.101],[-35.668,46.373],[-22.327,54.074],[-18.523,54.12],[-11.019,56.133],[-7.7,57.99],[7.7,57.99],[11.019,56.133],[18.523,54.12],[22.327,54.074],[35.668,46.373],[37.609,43.101],[43.099,37.611],[46.372,35.669],[54.072,22.328],[54.118,18.524],[56.132,11.019],[57.988,7.703],[57.988,-7.7],[56.132,-11.016],[54.118,-18.522],[54.072,-22.324],[46.372,-35.663],[43.099,-37.605],[37.609,-43.1],[35.668,-46.369],[22.327,-54.07],[18.523,-54.117],[11.019,-56.128]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":15},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,104.003]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets clock","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":25,"nm":"Drop Shadow","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 2","np":8,"mn":"ADBE Drop Shadow","ix":2,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 3","np":8,"mn":"ADBE Drop Shadow","ix":3,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 4","np":8,"mn":"ADBE Drop Shadow","ix":4,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 5","np":8,"mn":"ADBE Drop Shadow","ix":5,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 6","np":8,"mn":"ADBE Drop Shadow","ix":6,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 7","np":8,"mn":"ADBE Drop Shadow","ix":7,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 8","np":8,"mn":"ADBE Drop Shadow","ix":8,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 9","np":8,"mn":"ADBE Drop Shadow","ix":9,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 10","np":8,"mn":"ADBE Drop Shadow","ix":10,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 11","np":8,"mn":"ADBE Drop Shadow","ix":11,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 12","np":8,"mn":"ADBE Drop Shadow","ix":12,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 13","np":8,"mn":"ADBE Drop Shadow","ix":13,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 14","np":8,"mn":"ADBE Drop Shadow","ix":14,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]}],"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 7","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,128.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,56.002]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[156,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[60,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Scale Up","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":250,"s":[85,85,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":256,"s":[91,91,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":286,"s":[100,100,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[96,96,100]},{"t":416,"s":[90,90,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"TrackpadBack_Success_Checkmark","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,198.5,0]},"a":{"a":0,"k":[95,95,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":190,"h":190,"ip":6,"op":50,"st":6,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":3}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[0,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":49,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450982481,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"matte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"Back_LofiApp","parent":4,"tt":1,"tp":4,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":280,"s":[10,10,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[10,10,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[46,46,100]},{"t":416,"s":[100,100,100]}]}},"ao":0,"w":504,"h":315,"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"behindApp","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":259,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":386,"s":[0]},{"t":397,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[503.5,314.5]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Back_LofiLauncher","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8146cc5..e56b638 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1103,6 +1103,12 @@
<!-- Priority modes: label for an inactive mode [CHAR LIMIT=35] -->
<string name="zen_mode_off">Off</string>
+ <!-- Priority modes: label for a mode that needs to be set up [CHAR LIMIT=35] -->
+ <string name="zen_mode_set_up">Set up</string>
+
+ <!-- Priority modes: label for a mode that cannot be manually turned on [CHAR LIMIT=35] -->
+ <string name="zen_mode_no_manual_invocation">Manage in settings</string>
+
<!-- Zen mode: Priority only introduction message on first use -->
<string name="zen_priority_introduction">You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events, and callers you specify. You\'ll still hear anything you choose to play including music, videos, and games.</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 36912ac..c428705d 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1386,9 +1386,13 @@
<item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
</style>
- <style name="TextAppearance.InternetDialog.Active"/>
+ <style name="TextAppearance.InternetDialog.Active">
+ <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
+ </style>
- <style name="TextAppearance.InternetDialog.Secondary.Active"/>
+ <style name="TextAppearance.InternetDialog.Secondary.Active">
+ <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
+ </style>
<style name="FgsManagerDialogTitle">
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
@@ -1424,17 +1428,32 @@
<item name="android:orientation">horizontal</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+ </style>
+
+ <style name="BluetoothTileDialog.Device.Active">
+ <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
</style>
<style name="BluetoothTileDialog.DeviceName">
<item name="android:textSize">14sp</item>
<item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
</style>
<style name="BluetoothTileDialog.DeviceSummary">
<item name="android:ellipsize">end</item>
<item name="android:maxLines">2</item>
<item name="android:textAppearance">@style/TextAppearance.Dialog.Body.Message</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
+ </style>
+
+ <style name="BluetoothTileDialog.DeviceName.Active">
+ <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
+ </style>
+
+ <style name="BluetoothTileDialog.DeviceSummary.Active">
+ <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
</style>
<style name="BroadcastDialog">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 0f11717..1342dd0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -287,7 +287,7 @@
/**
* Helper used to receive device state info from {@link DeviceStateManager}.
*/
- static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback {
+ public static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback {
@Nullable
private final DisplayAddress.Physical mRearDisplayPhysicalAddress;
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardQuickAffordancesLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardQuickAffordancesLogger.kt
new file mode 100644
index 0000000..c11cf55
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardQuickAffordancesLogger.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.keyguard.logging
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.KeyguardQuickAffordancesLog
+import javax.inject.Inject
+
+class KeyguardQuickAffordancesLogger
+@Inject
+constructor(
+ @KeyguardQuickAffordancesLog val buffer: LogBuffer,
+) {
+ fun logQuickAffordanceTapped(configKey: String?) {
+ val (slotId, affordanceId) = configKey?.decode() ?: ("" to "")
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = affordanceId
+ str2 = slotId
+ },
+ { "QuickAffordance tapped with id: $str1, in slot: $str2" }
+ )
+ }
+
+ fun logQuickAffordanceTriggered(slotId: String, affordanceId: String) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = affordanceId
+ str2 = slotId
+ },
+ { "QuickAffordance triggered with id: $str1, in slot: $str2" }
+ )
+ }
+
+ fun logQuickAffordanceSelected(slotId: String, affordanceId: String) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = affordanceId
+ str2 = slotId
+ },
+ { "QuickAffordance selected with id: $str1, in slot: $str2" }
+ )
+ }
+
+ private fun String.decode(): Pair<String, String> {
+ val splitUp = this.split(DELIMITER)
+ return Pair(splitUp[0], splitUp[1])
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordancesLogger"
+ private const val DELIMITER = "::"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/activatable/Activatable.kt b/packages/SystemUI/src/com/android/systemui/activatable/Activatable.kt
new file mode 100644
index 0000000..dc2d931
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/activatable/Activatable.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.activatable
+
+/** Defines interface for classes that can be activated to do coroutine work. */
+interface Activatable {
+
+ /**
+ * Activates this object.
+ *
+ * Serves as an entrypoint to kick off coroutine work that the object requires in order to keep
+ * its state fresh and/or perform side-effects.
+ *
+ * The method suspends and doesn't return until all work required by the object is finished. In
+ * most cases, it's expected for the work to remain ongoing forever so this method will forever
+ * suspend its caller until the coroutine that called it is canceled.
+ *
+ * Implementations could follow this pattern:
+ * ```kotlin
+ * override suspend fun activate() {
+ * coroutineScope {
+ * launch { ... }
+ * launch { ... }
+ * launch { ... }
+ * }
+ * }
+ * ```
+ *
+ * **Must be invoked** by the owner of the object when the object is to become active.
+ * Similarly, the work must be canceled by the owner when the objects is to be deactivated.
+ *
+ * One way to have a parent call this would be by using a `LaunchedEffect` in Compose:
+ * ```kotlin
+ * @Composable
+ * fun MyUi(activatable: Activatable) {
+ * LaunchedEffect(activatable) {
+ * activatable.activate()
+ * }
+ * }
+ * ```
+ */
+ suspend fun activate()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
deleted file mode 100644
index 636bc5b..0000000
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.ambient.touch;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.util.Log;
-import android.view.GestureDetector;
-import android.view.InputEvent;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.internal.logging.UiEvent;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Flags;
-import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule;
-import com.android.systemui.ambient.touch.scrim.ScrimController;
-import com.android.systemui.ambient.touch.scrim.ScrimManager;
-import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.wm.shell.animation.FlingAnimationUtils;
-
-import java.util.Optional;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * Monitor for tracking touches on the DreamOverlay to bring up the bouncer.
- */
-public class BouncerSwipeTouchHandler implements TouchHandler {
- /**
- * An interface for creating ValueAnimators.
- */
- public interface ValueAnimatorCreator {
- /**
- * Creates {@link ValueAnimator}.
- */
- ValueAnimator create(float start, float finish);
- }
-
- /**
- * An interface for obtaining VelocityTrackers.
- */
- public interface VelocityTrackerFactory {
- /**
- * Obtains {@link VelocityTracker}.
- */
- VelocityTracker obtain();
- }
-
- public static final float FLING_PERCENTAGE_THRESHOLD = 0.5f;
-
- private static final String TAG = "BouncerSwipeTouchHandler";
- private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final LockPatternUtils mLockPatternUtils;
- private final UserTracker mUserTracker;
- private final float mBouncerZoneScreenPercentage;
- private final float mMinBouncerZoneScreenPercentage;
-
- private final ScrimManager mScrimManager;
- private ScrimController mCurrentScrimController;
- private float mCurrentExpansion;
- private final Optional<CentralSurfaces> mCentralSurfaces;
-
- private VelocityTracker mVelocityTracker;
-
- private final FlingAnimationUtils mFlingAnimationUtils;
- private final FlingAnimationUtils mFlingAnimationUtilsClosing;
-
- private Boolean mCapture;
- private Boolean mExpanded;
-
- private TouchSession mTouchSession;
-
- private final ValueAnimatorCreator mValueAnimatorCreator;
-
- private final VelocityTrackerFactory mVelocityTrackerFactory;
-
- private final UiEventLogger mUiEventLogger;
-
- private final ActivityStarter mActivityStarter;
-
- private final ScrimManager.Callback mScrimManagerCallback = new ScrimManager.Callback() {
- @Override
- public void onScrimControllerChanged(ScrimController controller) {
- if (mCurrentScrimController != null) {
- mCurrentScrimController.reset();
- }
-
- mCurrentScrimController = controller;
- }
- };
-
- private final GestureDetector.OnGestureListener mOnGestureListener =
- new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
- float distanceY) {
- if (mCapture == null) {
- if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
- mCapture = Math.abs(distanceY) > Math.abs(distanceX)
- && distanceY > 0;
- } else {
- // If the user scrolling favors a vertical direction, begin capturing
- // scrolls.
- mCapture = Math.abs(distanceY) > Math.abs(distanceX);
- }
- if (mCapture) {
- // reset expanding
- mExpanded = false;
- // Since the user is dragging the bouncer up, set scrimmed to false.
- mCurrentScrimController.show();
- }
- }
-
- if (!mCapture) {
- return false;
- }
-
- // Don't set expansion for downward scroll.
- if (e1.getY() < e2.getY()) {
- return true;
- }
-
- if (!mCentralSurfaces.isPresent()) {
- return true;
- }
-
- // If scrolling up and keyguard is not locked, dismiss both keyguard and the
- // dream since there's no bouncer to show.
- if (e1.getY() > e2.getY()
- && !mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
- mActivityStarter.executeRunnableDismissingKeyguard(
- () -> mCentralSurfaces.get().awakenDreams(),
- /* cancelAction= */ null,
- /* dismissShade= */ true,
- /* afterKeyguardGone= */ true,
- /* deferred= */ false);
- return true;
- }
-
- // For consistency, we adopt the expansion definition found in the
- // PanelViewController. In this case, expansion refers to the view above the
- // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
- // is fully hidden at full expansion (1) and fully visible when fully collapsed
- // (0).
- final float screenTravelPercentage = Math.abs(e1.getY() - e2.getY())
- / mTouchSession.getBounds().height();
- setPanelExpansion(1 - screenTravelPercentage);
- return true;
- }
- };
-
- private void setPanelExpansion(float expansion) {
- mCurrentExpansion = expansion;
- ShadeExpansionChangeEvent event =
- new ShadeExpansionChangeEvent(
- /* fraction= */ mCurrentExpansion,
- /* expanded= */ mExpanded,
- /* tracking= */ true);
- mCurrentScrimController.expand(event);
- }
-
-
- @VisibleForTesting
- public enum DreamEvent implements UiEventLogger.UiEventEnum {
- @UiEvent(doc = "The screensaver has been swiped up.")
- DREAM_SWIPED(988),
-
- @UiEvent(doc = "The bouncer has become fully visible over dream.")
- DREAM_BOUNCER_FULLY_VISIBLE(1056);
-
- private final int mId;
-
- DreamEvent(int id) {
- mId = id;
- }
-
- @Override
- public int getId() {
- return mId;
- }
- }
-
- @Inject
- public BouncerSwipeTouchHandler(
- ScrimManager scrimManager,
- Optional<CentralSurfaces> centralSurfaces,
- NotificationShadeWindowController notificationShadeWindowController,
- ValueAnimatorCreator valueAnimatorCreator,
- VelocityTrackerFactory velocityTrackerFactory,
- LockPatternUtils lockPatternUtils,
- UserTracker userTracker,
- @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
- FlingAnimationUtils flingAnimationUtils,
- @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
- FlingAnimationUtils flingAnimationUtilsClosing,
- @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
- @Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
- UiEventLogger uiEventLogger,
- ActivityStarter activityStarter) {
- mCentralSurfaces = centralSurfaces;
- mScrimManager = scrimManager;
- mNotificationShadeWindowController = notificationShadeWindowController;
- mLockPatternUtils = lockPatternUtils;
- mUserTracker = userTracker;
- mBouncerZoneScreenPercentage = swipeRegionPercentage;
- mMinBouncerZoneScreenPercentage = minRegionPercentage;
- mFlingAnimationUtils = flingAnimationUtils;
- mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
- mValueAnimatorCreator = valueAnimatorCreator;
- mVelocityTrackerFactory = velocityTrackerFactory;
- mUiEventLogger = uiEventLogger;
- mActivityStarter = activityStarter;
- }
-
- @Override
- public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
- final int width = bounds.width();
- final int height = bounds.height();
- final int minAllowableBottom = Math.round(height * (1 - mMinBouncerZoneScreenPercentage));
-
- final Rect normalRegion = new Rect(0,
- Math.round(height * (1 - mBouncerZoneScreenPercentage)),
- width, height);
-
- if (exclusionRect != null) {
- int lowestBottom = Math.min(Math.max(0, exclusionRect.bottom), minAllowableBottom);
- normalRegion.top = Math.max(normalRegion.top, lowestBottom);
- }
- region.union(normalRegion);
- }
-
-
- @Override
- public void onSessionStart(TouchSession session) {
- mVelocityTracker = mVelocityTrackerFactory.obtain();
- mTouchSession = session;
- mVelocityTracker.clear();
-
- if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
- mNotificationShadeWindowController.setForcePluginOpen(true, this);
- }
-
- mScrimManager.addCallback(mScrimManagerCallback);
- mCurrentScrimController = mScrimManager.getCurrentController();
-
- session.registerCallback(() -> {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- mScrimManager.removeCallback(mScrimManagerCallback);
- mCapture = null;
- mTouchSession = null;
-
- if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
- mNotificationShadeWindowController.setForcePluginOpen(false, this);
- }
- });
-
- session.registerGestureListener(mOnGestureListener);
- session.registerInputListener(ev -> onMotionEvent(ev));
-
- }
-
- private void onMotionEvent(InputEvent event) {
- if (!(event instanceof MotionEvent)) {
- Log.e(TAG, "non MotionEvent received:" + event);
- return;
- }
-
- final MotionEvent motionEvent = (MotionEvent) event;
-
- switch (motionEvent.getAction()) {
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- mTouchSession.pop();
- // If we are not capturing any input, there is no need to consider animating to
- // finish transition.
- if (mCapture == null || !mCapture) {
- break;
- }
-
- // We must capture the resulting velocities as resetMonitor() will clear these
- // values.
- mVelocityTracker.computeCurrentVelocity(1000);
- final float verticalVelocity = mVelocityTracker.getYVelocity();
- final float horizontalVelocity = mVelocityTracker.getXVelocity();
-
- final float velocityVector =
- (float) Math.hypot(horizontalVelocity, verticalVelocity);
-
- mExpanded = !flingRevealsOverlay(verticalVelocity, velocityVector);
- final float expansion = mExpanded
- ? KeyguardBouncerConstants.EXPANSION_VISIBLE
- : KeyguardBouncerConstants.EXPANSION_HIDDEN;
-
- // Log the swiping up to show Bouncer event.
- if (expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
- mUiEventLogger.log(DreamEvent.DREAM_SWIPED);
- }
-
- flingToExpansion(verticalVelocity, expansion);
- break;
- default:
- mVelocityTracker.addMovement(motionEvent);
- break;
- }
- }
-
- private ValueAnimator createExpansionAnimator(float targetExpansion) {
- final ValueAnimator animator =
- mValueAnimatorCreator.create(mCurrentExpansion, targetExpansion);
- animator.addUpdateListener(
- animation -> {
- float expansionFraction = (float) animation.getAnimatedValue();
- setPanelExpansion(expansionFraction);
- });
- if (targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
- animator.addListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mUiEventLogger.log(DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
- }
- });
- }
- return animator;
- }
-
- protected boolean flingRevealsOverlay(float velocity, float velocityVector) {
- // Fully expand the space above the bouncer, if the user has expanded the bouncer less
- // than halfway or final velocity was positive, indicating a downward direction.
- if (Math.abs(velocityVector) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
- return mCurrentExpansion > FLING_PERCENTAGE_THRESHOLD;
- } else {
- return velocity > 0;
- }
- }
-
- protected void flingToExpansion(float velocity, float expansion) {
- if (!mCentralSurfaces.isPresent()) {
- return;
- }
-
- // Don't set expansion if the user doesn't have a pin/password set.
- if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
- return;
- }
-
- // The animation utils deal in pixel units, rather than expansion height.
- final float viewHeight = mTouchSession.getBounds().height();
- final float currentHeight = viewHeight * mCurrentExpansion;
- final float targetHeight = viewHeight * expansion;
- final ValueAnimator animator = createExpansionAnimator(expansion);
- if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
- // Hides the bouncer, i.e., fully expands the space above the bouncer.
- mFlingAnimationUtilsClosing.apply(animator, currentHeight, targetHeight, velocity,
- viewHeight);
- } else {
- // Shows the bouncer, i.e., fully collapses the space above the bouncer.
- mFlingAnimationUtils.apply(
- animator, currentHeight, targetHeight, velocity, viewHeight);
- }
-
- animator.start();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
new file mode 100644
index 0000000..d5790a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
@@ -0,0 +1,371 @@
+/*
+ * 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.ambient.touch
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.graphics.Region
+import android.util.Log
+import android.view.GestureDetector
+import android.view.GestureDetector.SimpleOnGestureListener
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Flags
+import com.android.systemui.ambient.touch.TouchHandler.TouchSession
+import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule
+import com.android.systemui.ambient.touch.scrim.ScrimController
+import com.android.systemui.ambient.touch.scrim.ScrimManager
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.wm.shell.animation.FlingAnimationUtils
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.math.abs
+import kotlin.math.hypot
+import kotlin.math.max
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** Monitor for tracking touches on the DreamOverlay to bring up the bouncer. */
+class BouncerSwipeTouchHandler
+@Inject
+constructor(
+ scope: CoroutineScope,
+ private val scrimManager: ScrimManager,
+ private val centralSurfaces: Optional<CentralSurfaces>,
+ private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val valueAnimatorCreator: ValueAnimatorCreator,
+ private val velocityTrackerFactory: VelocityTrackerFactory,
+ private val lockPatternUtils: LockPatternUtils,
+ private val userTracker: UserTracker,
+ private val communalViewModel: CommunalViewModel,
+ @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+ private val flingAnimationUtils: FlingAnimationUtils,
+ @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+ private val flingAnimationUtilsClosing: FlingAnimationUtils,
+ @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION)
+ private val bouncerZoneScreenPercentage: Float,
+ @param:Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE)
+ private val minBouncerZoneScreenPercentage: Float,
+ private val uiEventLogger: UiEventLogger,
+ private val activityStarter: ActivityStarter
+) : TouchHandler {
+ /** An interface for creating ValueAnimators. */
+ interface ValueAnimatorCreator {
+ /** Creates [ValueAnimator]. */
+ fun create(start: Float, finish: Float): ValueAnimator
+ }
+
+ /** An interface for obtaining VelocityTrackers. */
+ interface VelocityTrackerFactory {
+ /** Obtains [VelocityTracker]. */
+ fun obtain(): VelocityTracker?
+ }
+
+ private var currentScrimController: ScrimController? = null
+ private var currentExpansion = 0f
+ private var velocityTracker: VelocityTracker? = null
+ private var capture: Boolean? = null
+ private var expanded: Boolean = false
+ private var touchSession: TouchSession? = null
+ private val scrimManagerCallback =
+ ScrimManager.Callback { controller ->
+ currentScrimController?.reset()
+
+ currentScrimController = controller
+ }
+
+ /** Determines whether the touch handler should process touches in fullscreen swiping mode */
+ private var touchAvailable = false
+
+ private val onGestureListener: GestureDetector.OnGestureListener =
+ object : SimpleOnGestureListener() {
+ override fun onScroll(
+ e1: MotionEvent?,
+ e2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
+ if (capture == null) {
+ capture =
+ if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
+ (abs(distanceY.toDouble()) > abs(distanceX.toDouble()) &&
+ distanceY > 0) &&
+ if (Flags.hubmodeFullscreenVerticalSwipe()) touchAvailable else true
+ } else {
+ // If the user scrolling favors a vertical direction, begin capturing
+ // scrolls.
+ abs(distanceY.toDouble()) > abs(distanceX.toDouble())
+ }
+ if (capture == true) {
+ // reset expanding
+ expanded = false
+ // Since the user is dragging the bouncer up, set scrimmed to false.
+ currentScrimController?.show()
+ }
+ }
+ if (capture != true) {
+ return false
+ }
+
+ if (!centralSurfaces.isPresent) {
+ return true
+ }
+
+ e1?.apply outer@{
+ // Don't set expansion for downward scroll.
+ if (y < e2.y) {
+ return true
+ }
+
+ // If scrolling up and keyguard is not locked, dismiss both keyguard and the
+ // dream since there's no bouncer to show.
+ if (y > e2.y && !lockPatternUtils.isSecure(userTracker.userId)) {
+ activityStarter.executeRunnableDismissingKeyguard(
+ { centralSurfaces.get().awakenDreams() },
+ /* cancelAction= */ null,
+ /* dismissShade= */ true,
+ /* afterKeyguardGone= */ true,
+ /* deferred= */ false
+ )
+ return true
+ }
+
+ // For consistency, we adopt the expansion definition found in the
+ // PanelViewController. In this case, expansion refers to the view above the
+ // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
+ // is fully hidden at full expansion (1) and fully visible when fully collapsed
+ // (0).
+ touchSession?.apply {
+ val screenTravelPercentage =
+ (abs((this@outer.y - e2.y).toDouble()) / getBounds().height()).toFloat()
+ setPanelExpansion(1 - screenTravelPercentage)
+ }
+ }
+
+ return true
+ }
+ }
+
+ init {
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ scope.launch {
+ communalViewModel.glanceableTouchAvailable.collect {
+ onGlanceableTouchAvailable(it)
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun onGlanceableTouchAvailable(available: Boolean) {
+ touchAvailable = available
+ }
+
+ private fun setPanelExpansion(expansion: Float) {
+ currentExpansion = expansion
+ val event =
+ ShadeExpansionChangeEvent(
+ /* fraction= */ currentExpansion,
+ /* expanded= */ expanded,
+ /* tracking= */ true
+ )
+ currentScrimController?.expand(event)
+ }
+
+ @VisibleForTesting
+ enum class DreamEvent(private val mId: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The screensaver has been swiped up.") DREAM_SWIPED(988),
+ @UiEvent(doc = "The bouncer has become fully visible over dream.")
+ DREAM_BOUNCER_FULLY_VISIBLE(1056);
+
+ override fun getId(): Int {
+ return mId
+ }
+ }
+
+ override fun getTouchInitiationRegion(bounds: Rect, region: Region, exclusionRect: Rect?) {
+ val width = bounds.width()
+ val height = bounds.height()
+ val minAllowableBottom = Math.round(height * (1 - minBouncerZoneScreenPercentage))
+ val normalRegion =
+ Rect(0, Math.round(height * (1 - bouncerZoneScreenPercentage)), width, height)
+
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ region.op(bounds, Region.Op.UNION)
+ exclusionRect?.apply { region.op(this, Region.Op.DIFFERENCE) }
+ }
+
+ if (exclusionRect != null) {
+ val lowestBottom =
+ min(max(0.0, exclusionRect.bottom.toDouble()), minAllowableBottom.toDouble())
+ .toInt()
+ normalRegion.top = max(normalRegion.top.toDouble(), lowestBottom.toDouble()).toInt()
+ }
+ region.union(normalRegion)
+ }
+
+ override fun onSessionStart(session: TouchSession) {
+ velocityTracker = velocityTrackerFactory.obtain()
+ touchSession = session
+ velocityTracker?.apply { clear() }
+ if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
+ notificationShadeWindowController.setForcePluginOpen(true, this)
+ }
+ scrimManager.addCallback(scrimManagerCallback)
+ currentScrimController = scrimManager.currentController
+ session.registerCallback {
+ velocityTracker?.apply { recycle() }
+ velocityTracker = null
+
+ scrimManager.removeCallback(scrimManagerCallback)
+ capture = null
+ touchSession = null
+ if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
+ notificationShadeWindowController.setForcePluginOpen(false, this)
+ }
+ }
+ session.registerGestureListener(onGestureListener)
+ session.registerInputListener { ev: InputEvent -> onMotionEvent(ev) }
+ }
+
+ private fun onMotionEvent(event: InputEvent) {
+ if (event !is MotionEvent) {
+ Log.e(TAG, "non MotionEvent received:$event")
+ return
+ }
+ val motionEvent = event
+ when (motionEvent.action) {
+ MotionEvent.ACTION_CANCEL,
+ MotionEvent.ACTION_UP -> {
+ if (Flags.hubmodeFullscreenVerticalSwipe() && capture == true) {
+ communalViewModel.onResetTouchState()
+ }
+ touchSession?.apply { pop() }
+ // If we are not capturing any input, there is no need to consider animating to
+ // finish transition.
+ if (capture == null || !capture!!) {
+ return
+ }
+
+ // We must capture the resulting velocities as resetMonitor() will clear these
+ // values.
+ velocityTracker!!.computeCurrentVelocity(1000)
+ val verticalVelocity = velocityTracker!!.yVelocity
+ val horizontalVelocity = velocityTracker!!.xVelocity
+ val velocityVector =
+ hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat()
+ expanded = !flingRevealsOverlay(verticalVelocity, velocityVector)
+ val expansion =
+ if (expanded!!) KeyguardBouncerConstants.EXPANSION_VISIBLE
+ else KeyguardBouncerConstants.EXPANSION_HIDDEN
+
+ // Log the swiping up to show Bouncer event.
+ if (expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
+ uiEventLogger.log(DreamEvent.DREAM_SWIPED)
+ }
+ flingToExpansion(verticalVelocity, expansion)
+ }
+ else -> velocityTracker!!.addMovement(motionEvent)
+ }
+ }
+
+ private fun createExpansionAnimator(targetExpansion: Float): ValueAnimator {
+ val animator = valueAnimatorCreator.create(currentExpansion, targetExpansion)
+ animator.addUpdateListener { animation: ValueAnimator ->
+ val expansionFraction = animation.animatedValue as Float
+ setPanelExpansion(expansionFraction)
+ }
+ if (targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ uiEventLogger.log(DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE)
+ }
+ }
+ )
+ }
+ return animator
+ }
+
+ protected fun flingRevealsOverlay(velocity: Float, velocityVector: Float): Boolean {
+ // Fully expand the space above the bouncer, if the user has expanded the bouncer less
+ // than halfway or final velocity was positive, indicating a downward direction.
+ return if (abs(velocityVector.toDouble()) < flingAnimationUtils.minVelocityPxPerSecond) {
+ currentExpansion > FLING_PERCENTAGE_THRESHOLD
+ } else {
+ velocity > 0
+ }
+ }
+
+ protected fun flingToExpansion(velocity: Float, expansion: Float) {
+ if (!centralSurfaces.isPresent) {
+ return
+ }
+
+ // Don't set expansion if the user doesn't have a pin/password set.
+ if (!lockPatternUtils.isSecure(userTracker.userId)) {
+ return
+ }
+
+ touchSession?.apply {
+ // The animation utils deal in pixel units, rather than expansion height.
+ val viewHeight = getBounds().height().toFloat()
+ val currentHeight = viewHeight * currentExpansion
+ val targetHeight = viewHeight * expansion
+ val animator = createExpansionAnimator(expansion)
+ if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
+ // Hides the bouncer, i.e., fully expands the space above the bouncer.
+ flingAnimationUtilsClosing.apply(
+ animator,
+ currentHeight,
+ targetHeight,
+ velocity,
+ viewHeight
+ )
+ } else {
+ // Shows the bouncer, i.e., fully collapses the space above the bouncer.
+ flingAnimationUtils.apply(
+ animator,
+ currentHeight,
+ targetHeight,
+ velocity,
+ viewHeight
+ )
+ }
+ animator.start()
+ }
+ }
+
+ companion object {
+ const val FLING_PERCENTAGE_THRESHOLD = 0.5f
+ private const val TAG = "BouncerSwipeTouchHandler"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
deleted file mode 100644
index baca959..0000000
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.ambient.touch;
-
-import static com.android.systemui.ambient.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT;
-
-import android.app.DreamManager;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
-import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-
-import java.util.Optional;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * {@link ShadeTouchHandler} is responsible for handling swipe down gestures over dream
- * to bring down the shade.
- */
-public class ShadeTouchHandler implements TouchHandler {
- private final Optional<CentralSurfaces> mSurfaces;
- private final ShadeViewController mShadeViewController;
- private final DreamManager mDreamManager;
- private final int mInitiationHeight;
- private final CommunalSettingsInteractor
- mCommunalSettingsInteractor;
-
- /**
- * Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
- */
- private Boolean mCapture;
-
- @Inject
- ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces,
- ShadeViewController shadeViewController,
- DreamManager dreamManager,
- CommunalSettingsInteractor communalSettingsInteractor,
- @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) {
- mSurfaces = centralSurfaces;
- mShadeViewController = shadeViewController;
- mDreamManager = dreamManager;
- mCommunalSettingsInteractor = communalSettingsInteractor;
- mInitiationHeight = initiationHeight;
- }
-
- @Override
- public void onSessionStart(TouchSession session) {
- if (mSurfaces.isEmpty()) {
- session.pop();
- return;
- }
-
- session.registerCallback(() -> mCapture = null);
-
- session.registerInputListener(ev -> {
- if (ev instanceof MotionEvent) {
- if (mCapture != null && mCapture) {
- sendTouchEvent((MotionEvent) ev);
- }
- if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP
- || ((MotionEvent) ev).getAction() == MotionEvent.ACTION_CANCEL) {
- session.pop();
- }
- }
- });
-
- session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
- float distanceY) {
- if (mCapture == null) {
- // Only capture swipes that are going downwards.
- mCapture = Math.abs(distanceY) > Math.abs(distanceX) && distanceY < 0;
- if (mCapture) {
- // Send the initial touches over, as the input listener has already
- // processed these touches.
- sendTouchEvent(e1);
- sendTouchEvent(e2);
- }
- }
- return mCapture;
- }
-
- @Override
- public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX,
- float velocityY) {
- return mCapture;
- }
- });
- }
-
- private void sendTouchEvent(MotionEvent event) {
- if (mCommunalSettingsInteractor.isCommunalFlagEnabled() && !mDreamManager.isDreaming()) {
- // Send touches to central surfaces only when on the glanceable hub while not dreaming.
- // While sending touches where while dreaming will open the shade, the shade
- // while closing if opened then closed in the same gesture.
- mSurfaces.get().handleExternalShadeWindowTouch(event);
- } else {
- // Send touches to the shade view when dreaming.
- mShadeViewController.handleExternalTouch(event);
- }
- }
-
- @Override
- public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
- final Rect outBounds = new Rect(bounds);
- outBounds.inset(0, 0, 0, outBounds.height() - mInitiationHeight);
- region.op(outBounds, Region.Op.UNION);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
new file mode 100644
index 0000000..06b41de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.ambient.touch
+
+import android.app.DreamManager
+import android.graphics.Rect
+import android.graphics.Region
+import android.view.GestureDetector.SimpleOnGestureListener
+import android.view.InputEvent
+import android.view.MotionEvent
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Flags
+import com.android.systemui.ambient.touch.TouchHandler.TouchSession
+import com.android.systemui.ambient.touch.dagger.ShadeModule
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.math.abs
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * [ShadeTouchHandler] is responsible for handling swipe down gestures over dream to bring down the
+ * shade.
+ */
+class ShadeTouchHandler
+@Inject
+constructor(
+ scope: CoroutineScope,
+ private val surfaces: Optional<CentralSurfaces>,
+ private val shadeViewController: ShadeViewController,
+ private val dreamManager: DreamManager,
+ private val communalViewModel: CommunalViewModel,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
+ @param:Named(ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT)
+ private val initiationHeight: Int
+) : TouchHandler {
+ /**
+ * Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
+ */
+ private var capture: Boolean? = null
+
+ /** Determines whether the touch handler should process touches in fullscreen swiping mode */
+ private var touchAvailable = false
+
+ init {
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ scope.launch {
+ communalViewModel.glanceableTouchAvailable.collect {
+ onGlanceableTouchAvailable(it)
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun onGlanceableTouchAvailable(available: Boolean) {
+ touchAvailable = available
+ }
+
+ override fun onSessionStart(session: TouchSession) {
+ if (surfaces.isEmpty) {
+ session.pop()
+ return
+ }
+ session.registerCallback { capture = null }
+ session.registerInputListener { ev: InputEvent? ->
+ if (ev is MotionEvent) {
+ if (capture == true) {
+ sendTouchEvent(ev)
+ }
+ if (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL) {
+ if (capture == true) {
+ communalViewModel.onResetTouchState()
+ }
+ session.pop()
+ }
+ }
+ }
+ session.registerGestureListener(
+ object : SimpleOnGestureListener() {
+ override fun onScroll(
+ e1: MotionEvent?,
+ e2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
+ if (capture == null) {
+ // Only capture swipes that are going downwards.
+ capture =
+ abs(distanceY.toDouble()) > abs(distanceX.toDouble()) &&
+ distanceY < 0 &&
+ if (Flags.hubmodeFullscreenVerticalSwipe()) touchAvailable else true
+ if (capture == true) {
+ // Send the initial touches over, as the input listener has already
+ // processed these touches.
+ e1?.apply { sendTouchEvent(this) }
+ sendTouchEvent(e2)
+ }
+ }
+ return capture == true
+ }
+
+ override fun onFling(
+ e1: MotionEvent?,
+ e2: MotionEvent,
+ velocityX: Float,
+ velocityY: Float
+ ): Boolean {
+ return capture == true
+ }
+ }
+ )
+ }
+
+ private fun sendTouchEvent(event: MotionEvent) {
+ if (communalSettingsInteractor.isCommunalFlagEnabled() && !dreamManager.isDreaming) {
+ // Send touches to central surfaces only when on the glanceable hub while not dreaming.
+ // While sending touches where while dreaming will open the shade, the shade
+ // while closing if opened then closed in the same gesture.
+ surfaces.get().handleExternalShadeWindowTouch(event)
+ } else {
+ // Send touches to the shade view when dreaming.
+ shadeViewController.handleExternalTouch(event)
+ }
+ }
+
+ override fun getTouchInitiationRegion(bounds: Rect, region: Region, exclusionRect: Rect?) {
+ // If fullscreen swipe, use entire space minus exclusion region
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ region.op(bounds, Region.Op.UNION)
+
+ exclusionRect?.apply { region.op(this, Region.Op.DIFFERENCE) }
+ }
+
+ val outBounds = Rect(bounds)
+ outBounds.inset(0, 0, 0, outBounds.height() - initiationHeight)
+ region.op(outBounds, Region.Op.UNION)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
index a4924d1..ae21e56 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
@@ -17,12 +17,14 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.coroutineScope
import com.android.systemui.ambient.dagger.AmbientModule
import com.android.systemui.ambient.touch.TouchHandler
import dagger.Module
import dagger.Provides
import dagger.multibindings.ElementsIntoSet
import javax.inject.Named
+import kotlinx.coroutines.CoroutineScope
@Module
interface AmbientTouchModule {
@@ -33,6 +35,12 @@
return lifecycleOwner.lifecycle
}
+ @JvmStatic
+ @Provides
+ fun providesLifecycleScope(lifecycle: Lifecycle): CoroutineScope {
+ return lifecycle.coroutineScope
+ }
+
@Provides
@ElementsIntoSet
fun providesDreamTouchHandlers(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 430887d..ecfbd66 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -24,7 +24,6 @@
import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.Flags
import android.hardware.face.FaceManager
-import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
@@ -124,7 +123,6 @@
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
subtitleView.isSelected =
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
- descriptionView.movementMethod = ScrollingMovementMethod()
val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 3904ee1..b1cba2f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -915,7 +915,12 @@
event: MotionEvent,
touchExplorationEnabled: Boolean,
): Boolean {
- if (bpTalkback() && modalities.first().hasUdfps && touchExplorationEnabled) {
+ if (
+ bpTalkback() &&
+ modalities.first().hasUdfps &&
+ touchExplorationEnabled &&
+ !isAuthenticated.first().isAuthenticated
+ ) {
// TODO(b/315184924): Remove uses of UdfpsUtils
val scaledTouch =
udfpsUtils.getTouchInNativeCoordinates(
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index 911145b..f5b9a05 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -37,6 +37,7 @@
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import com.android.internal.R as InternalR
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
@@ -367,6 +368,7 @@
private val nameView = view.requireViewById<TextView>(R.id.bluetooth_device_name)
private val summaryView = view.requireViewById<TextView>(R.id.bluetooth_device_summary)
private val iconView = view.requireViewById<ImageView>(R.id.bluetooth_device_icon)
+ private val iconGear = view.requireViewById<ImageView>(R.id.gear_icon_image)
private val gearView = view.requireViewById<View>(R.id.gear_icon)
internal fun bind(
@@ -380,6 +382,36 @@
mutableDeviceItemClick.tryEmit(item)
uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED)
}
+
+ // updating icon colors
+ val tintColor =
+ com.android.settingslib.Utils.getColorAttr(
+ context,
+ if (item.isActive) InternalR.attr.materialColorOnPrimaryContainer
+ else InternalR.attr.materialColorOnSurface
+ )
+ .defaultColor
+
+ // update icons
+ iconView.apply {
+ item.iconWithDescription?.let {
+ setImageDrawable(it.first.apply { mutate()?.setTint(tintColor) })
+ contentDescription = it.second
+ }
+ }
+
+ iconGear.apply { drawable?.let { it.mutate()?.setTint(tintColor) } }
+
+ // update text styles
+ nameView.setTextAppearance(
+ if (item.isActive) R.style.BluetoothTileDialog_DeviceName_Active
+ else R.style.BluetoothTileDialog_DeviceName
+ )
+ summaryView.setTextAppearance(
+ if (item.isActive) R.style.BluetoothTileDialog_DeviceSummary_Active
+ else R.style.BluetoothTileDialog_DeviceSummary
+ )
+
accessibilityDelegate =
object : AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(
@@ -398,12 +430,7 @@
}
nameView.text = item.deviceName
summaryView.text = item.connectionSummary
- iconView.apply {
- item.iconWithDescription?.let {
- setImageDrawable(it.first)
- contentDescription = it.second
- }
- }
+
gearView.setOnClickListener {
deviceItemOnClickCallback.onDeviceItemGearClicked(item, it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index 0ea98d1..a78130f 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -52,4 +52,5 @@
val background: Int? = null,
var isEnabled: Boolean = true,
var actionAccessibilityLabel: String = "",
+ var isActive: Boolean = false
)
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 51b2280..d7893db 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -55,7 +55,8 @@
type: DeviceItemType,
connectionSummary: String,
background: Int,
- actionAccessibilityLabel: String
+ actionAccessibilityLabel: String,
+ isActive: Boolean
): DeviceItem {
return DeviceItem(
type = type,
@@ -68,7 +69,8 @@
},
background = background,
isEnabled = !cachedDevice.isBusy,
- actionAccessibilityLabel = actionAccessibilityLabel
+ actionAccessibilityLabel = actionAccessibilityLabel,
+ isActive = isActive
)
}
}
@@ -91,7 +93,8 @@
DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
cachedDevice.connectionSummary ?: "",
backgroundOn,
- context.getString(actionAccessibilityLabelDisconnect)
+ context.getString(actionAccessibilityLabelDisconnect),
+ isActive = true
)
}
}
@@ -116,7 +119,8 @@
cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
?: context.getString(audioSharing),
if (cachedDevice.isBusy) backgroundOffBusy else backgroundOn,
- ""
+ "",
+ isActive = !cachedDevice.isBusy
)
}
}
@@ -150,7 +154,8 @@
cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
?: context.getString(connected),
if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
- context.getString(actionAccessibilityLabelActivate)
+ context.getString(actionAccessibilityLabelActivate),
+ isActive = false
)
}
}
@@ -188,7 +193,8 @@
cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
?: context.getString(connected),
if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
- context.getString(actionAccessibilityLabelDisconnect)
+ context.getString(actionAccessibilityLabelDisconnect),
+ isActive = false
)
}
}
@@ -216,7 +222,8 @@
cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
?: context.getString(saved),
if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
- context.getString(actionAccessibilityLabelActivate)
+ context.getString(actionAccessibilityLabelActivate),
+ isActive = false
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 40a141d..e2089bb 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -24,7 +24,6 @@
import androidx.compose.ui.input.key.type
import androidx.core.graphics.drawable.toBitmap
import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
@@ -43,7 +42,6 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
-import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
@@ -67,7 +65,9 @@
/** Holds UI state and handles user input on bouncer UIs. */
class BouncerViewModel(
@Application private val applicationContext: Context,
- @Application private val applicationScope: CoroutineScope,
+ @Deprecated("TODO(b/354270224): remove this. Injecting CoroutineScope to view-models is banned")
+ @Application
+ private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
private val bouncerInteractor: BouncerInteractor,
private val inputMethodInteractor: InputMethodInteractor,
@@ -91,14 +91,13 @@
initialValue = null,
)
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- bouncerInteractor.dismissDestination
- .map(::destinationSceneMap)
- .stateIn(
- applicationScope,
- SharingStarted.WhileSubscribed(),
- initialValue = destinationSceneMap(Scenes.Lockscreen),
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ bouncerInteractor.dismissDestination.map { prevScene ->
+ mapOf(
+ Back to UserActionResult(prevScene),
+ Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
)
+ }
val message: BouncerMessageViewModel = bouncerMessageViewModel
@@ -328,8 +327,7 @@
{ message },
failedAttempts,
remainingAttempts,
- )
- ?: message
+ ) ?: message
} else {
message
}
@@ -346,8 +344,7 @@
.KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE,
{ message },
failedAttempts,
- )
- ?: message
+ ) ?: message
} else {
message
}
@@ -375,12 +372,6 @@
}
}
- private fun destinationSceneMap(prevScene: SceneKey) =
- mapOf(
- Back to UserActionResult(prevScene),
- Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
- )
-
/**
* Notifies that a key event has occurred.
*
@@ -390,8 +381,7 @@
return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
keyEvent.type,
keyEvent.nativeKeyEvent.keyCode
- )
- ?: false
+ ) ?: false
}
data class DialogViewModel(
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index bd0e729..04c6fa9 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -301,8 +301,8 @@
mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_EXPANDED);
setExpandedView(this::animateIn);
}
- mView.announceForAccessibility(
- getAccessibilityAnnouncement(mClipboardModel.getType()));
+ mWindow.withWindowAttached(() -> mView.announceForAccessibility(
+ getAccessibilityAnnouncement(mClipboardModel.getType())));
} else if (!mIsMinimized) {
setExpandedView(() -> {
});
@@ -320,8 +320,8 @@
setExpandedView();
}
animateIn();
- mView.announceForAccessibility(
- getAccessibilityAnnouncement(mClipboardModel.getType()));
+ mWindow.withWindowAttached(() -> mView.announceForAccessibility(
+ getAccessibilityAnnouncement(mClipboardModel.getType())));
} else if (!mIsMinimized) {
setExpandedView();
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index e0e1971..adb1ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -25,6 +25,7 @@
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
@@ -74,6 +75,13 @@
return onAnyConfigurationChange.mapLatest { repository.getDimensionPixelSize(resourceId) }
}
+ /** Emits the dimensional pixel size of the given resource, inverting it for RTL if necessary */
+ fun directionalDimensionPixelSize(originLayoutDirection: Int, resourceId: Int): Flow<Int> {
+ return dimensionPixelSize(resourceId).combine(layoutDirection) { size, direction ->
+ if (originLayoutDirection == direction) size else -size
+ }
+ }
+
/** Given a set of [resourceId]s, emit Map<ResourceId, DimensionPixelSize> on config change */
fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>> {
return onAnyConfigurationChange.mapLatest {
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/communal/log/CommunalLoggerStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
index 81feb44..2352841f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
@@ -19,14 +19,16 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.logging.UiEventLogger
import com.android.systemui.CoreStartable
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filterNotNull
@@ -40,12 +42,13 @@
@Inject
constructor(
@Background private val backgroundScope: CoroutineScope,
- private val communalInteractor: CommunalInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
+ private val keyguardInteractor: KeyguardInteractor,
private val uiEventLogger: UiEventLogger,
) : CoreStartable {
override fun start() {
- communalInteractor.transitionState
+ communalSceneInteractor.transitionState
.map { state ->
when {
state.isOnCommunal() -> CommunalUiEvent.COMMUNAL_HUB_SHOWN
@@ -60,22 +63,46 @@
.onEach { uiEvent -> uiEventLogger.log(uiEvent) }
.launchIn(backgroundScope)
- communalInteractor.transitionState
+ communalSceneInteractor.transitionState
.pairwise()
- .map { (old, new) ->
+ .combine(keyguardInteractor.isDreamingWithOverlay) { (old, new), isDreaming ->
when {
new.isOnCommunal() && old.isSwipingToCommunal() ->
- CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH
+ if (isDreaming) {
+ CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_FINISH
+ } else {
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH
+ }
new.isOnCommunal() && old.isSwipingFromCommunal() ->
- CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL
+ if (isDreaming) {
+ CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_CANCEL
+ } else {
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL
+ }
new.isNotOnCommunal() && old.isSwipingFromCommunal() ->
- CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH
+ if (isDreaming) {
+ CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_FINISH
+ } else {
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH
+ }
new.isNotOnCommunal() && old.isSwipingToCommunal() ->
- CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL
+ if (isDreaming) {
+ CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_CANCEL
+ } else {
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL
+ }
new.isSwipingToCommunal() && old.isNotOnCommunal() ->
- CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START
+ if (isDreaming) {
+ CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_START
+ } else {
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START
+ }
new.isSwipingFromCommunal() && old.isOnCommunal() ->
- CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START
+ if (isDreaming) {
+ CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_START
+ } else {
+ CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START
+ }
else -> null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
index 6e345fe..9ce8cf7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
@@ -56,6 +56,19 @@
)
}
+ /** Logs a tap widget event for metrics. No-op if widget is not loggable. */
+ fun logTapWidget(componentName: String, rank: Int) {
+ if (!componentName.isLoggable()) {
+ return
+ }
+
+ statsLogProxy.writeCommunalHubWidgetEventReported(
+ SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__TAP,
+ componentName,
+ rank,
+ )
+ }
+
/** Logs loggable widgets and the total widget count as a [StatsEvent]. */
fun logWidgetsSnapshot(
statsEvents: MutableList<StatsEvent>,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
index 4ab56cc..4711d88 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
@@ -54,14 +54,20 @@
COMMUNAL_HUB_SWIPE_UP_TO_BOUNCER(1573),
@UiEvent(doc = "User performs a swipe down gesture from top to enter shade")
COMMUNAL_HUB_SWIPE_DOWN_TO_SHADE(1574),
- @UiEvent(doc = "User performs a tap gesture on the UMO in Communal Hub")
- COMMUNAL_HUB_UMO_TAP(1858),
- @UiEvent(
- doc =
- "A transition from dream to Communal Hub starts. This can be triggered by a tap on " +
- "the dream."
- )
- FROM_DREAM_TO_COMMUNAL_HUB_TRANSITION_START(1859);
+ @UiEvent(doc = "User starts the swipe gesture to enter the Communal Hub from Dream")
+ DREAM_TO_COMMUNAL_HUB_SWIPE_START(1860),
+ @UiEvent(doc = "User finishes the swipe gesture to enter the Communal Hub from Dream")
+ DREAM_TO_COMMUNAL_HUB_SWIPE_FINISH(1861),
+ @UiEvent(doc = "User cancels the swipe gesture to enter the Communal Hub from Dream")
+ DREAM_TO_COMMUNAL_HUB_SWIPE_CANCEL(1862),
+ @UiEvent(doc = "User starts the swipe gesture to exit the Communal Hub to go to Dream")
+ COMMUNAL_HUB_TO_DREAM_SWIPE_START(1863),
+ @UiEvent(doc = "User finishes the swipe gesture to exit the Communal Hub to go to Dream")
+ COMMUNAL_HUB_TO_DREAM_SWIPE_FINISH(1864),
+ @UiEvent(doc = "User cancels the swipe gesture to exit the Communal Hub to go to Dream")
+ COMMUNAL_HUB_TO_DREAM_SWIPE_CANCEL(1865),
+ @UiEvent(doc = "A transition from Dream to Communal Hub starts due to dream awakening")
+ DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START(1866);
override fun getId(): Int {
return id
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 623e702..4be93cc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -29,9 +29,12 @@
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flowOf
/** The base view model for the communal hub. */
@@ -57,6 +60,26 @@
val selectedKey: StateFlow<String?>
get() = _selectedKey
+ private val _isTouchConsumed: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Whether an element inside the lazy grid is actively consuming touches */
+ val isTouchConsumed: Flow<Boolean> = _isTouchConsumed.asStateFlow()
+
+ private val _isNestedScrolling: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Whether the lazy grid is reporting scrolling within itself */
+ val isNestedScrolling: Flow<Boolean> = _isNestedScrolling.asStateFlow()
+
+ /**
+ * Whether touch is available to be consumed by a touch handler. Touch is available during
+ * nested scrolling as lazy grid reports this for all scroll directions that it detects. In the
+ * case that there is consumed scrolling on a nested element, such as an AndroidView, no nested
+ * scrolling will be reported. It is up to the flow consumer to determine whether the nested
+ * scroll can be applied. In the communal case, this would be identifying the scroll as
+ * vertical, which the lazy horizontal grid does not handle.
+ */
+ val glanceableTouchAvailable: Flow<Boolean> = anyOf(not(isTouchConsumed), isNestedScrolling)
+
/** Accessibility delegate to be set on CommunalAppWidgetHostView. */
open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null
@@ -139,6 +162,12 @@
priority: Int,
) {}
+ /** Called as the UI detects a tap event on the widget. */
+ open fun onTapWidget(
+ componentName: ComponentName,
+ priority: Int,
+ ) {}
+
/**
* Called as the UI requests reordering widgets.
*
@@ -194,4 +223,28 @@
fun setSelectedKey(key: String?) {
_selectedKey.value = key
}
+
+ /** Invoked once touches inside the lazy grid are consumed */
+ fun onHubTouchConsumed() {
+ if (_isTouchConsumed.value) {
+ return
+ }
+
+ _isTouchConsumed.value = true
+ }
+
+ /** Invoked when nested scrolling begins on the lazy grid */
+ fun onNestedScrolling() {
+ if (_isNestedScrolling.value) {
+ return
+ }
+
+ _isNestedScrolling.value = true
+ }
+
+ /** Resets nested scroll and touch consumption state */
+ fun onResetTouchState() {
+ _isTouchConsumed.value = false
+ _isNestedScrolling.value = false
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index e1408a0..bbd8596 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -19,6 +19,7 @@
import android.graphics.Color
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -58,9 +59,17 @@
dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
glanceableHubToDreamTransitionViewModel: GlanceableHubToDreamingTransitionViewModel,
communalInteractor: CommunalInteractor,
- communalSceneInteractor: CommunalSceneInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor
) {
+ /**
+ * Snaps to [CommunalScenes.Communal], showing the glanceable hub immediately without any
+ * transition.
+ */
+ fun snapToCommunal() {
+ communalSceneInteractor.snapToScene(CommunalScenes.Communal)
+ }
+
// Show UMO on glanceable hub immediately on transition into glanceable hub
private val showUmoFromOccludedToGlanceableHub: Flow<Boolean> =
keyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 1e087f7..3fc8b09 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.ui.viewmodel
+import android.content.ComponentName
import android.content.res.Resources
import android.os.Bundle
import android.view.View
@@ -25,6 +26,7 @@
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -92,6 +94,7 @@
private val shadeInteractor: ShadeInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@CommunalLog logBuffer: LogBuffer,
+ private val metricsLogger: CommunalMetricsLogger,
) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
private val _isMediaHostVisible =
@@ -260,6 +263,10 @@
}
}
+ override fun onTapWidget(componentName: ComponentName, priority: Int) {
+ metricsLogger.logTapWidget(componentName.flattenToString(), priority)
+ }
+
fun onClick() {
keyguardIndicationController.showActionToUnlock()
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/DensityUtils.kt b/packages/SystemUI/src/com/android/systemui/communal/util/DensityUtils.kt
new file mode 100644
index 0000000..57be7b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/DensityUtils.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.util
+
+import android.view.Display
+import android.view.WindowManagerGlobal
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+/**
+ * [DensityUtils] helps convert dp defined values to be consistent regardless of the set density.
+ */
+class DensityUtils {
+ companion object {
+ val Int.adjustedDp: Dp
+ get() = this.dp * scalingAdjustment
+
+ private val windowManagerService = WindowManagerGlobal.getWindowManagerService()
+ val scalingAdjustment
+ get() =
+ windowManagerService?.let { wm ->
+ wm.getInitialDisplayDensity(Display.DEFAULT_DISPLAY).toFloat() /
+ wm.getBaseDisplayDensity(Display.DEFAULT_DISPLAY)
+ } ?: 1F
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 3985769..03ef17b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -128,7 +128,7 @@
Box(
modifier =
Modifier.fillMaxSize()
- .background(LocalAndroidColorScheme.current.onSecondaryFixed),
+ .background(LocalAndroidColorScheme.current.surfaceDim),
) {
CommunalHub(
viewModel = communalViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
index abda44b..87aa5e2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
@@ -24,6 +24,7 @@
import android.view.View
import android.view.ViewGroup
import androidx.core.os.BuildCompat.isAtLeastS
+import com.android.systemui.communal.util.DensityUtils
import com.android.systemui.res.R
import kotlin.math.min
@@ -82,7 +83,8 @@
/** Get the radius of the rounded rectangle defined in the host's resource. */
private fun getOwnedEnforcedRadius(context: Context): Float {
val res: Resources = context.resources
- return res.getDimension(R.dimen.communal_enforced_rounded_corner_max_radius)
+ return res.getDimension(R.dimen.communal_enforced_rounded_corner_max_radius) *
+ DensityUtils.scalingAdjustment
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 2fbb75e..c2e1e33 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -29,7 +29,6 @@
import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
import com.android.systemui.settings.brightness.BrightnessDialog;
import com.android.systemui.telephony.ui.activity.SwitchToManagedProfileForCallActivity;
-import com.android.systemui.touchpad.tutorial.ui.view.TouchpadTutorialActivity;
import com.android.systemui.tuner.TunerActivity;
import com.android.systemui.usb.UsbAccessoryUriActivity;
import com.android.systemui.usb.UsbConfirmActivity;
@@ -157,10 +156,4 @@
@ClassKey(SwitchToManagedProfileForCallActivity.class)
public abstract Activity bindSwitchToManagedProfileForCallActivity(
SwitchToManagedProfileForCallActivity activity);
-
- /** Inject into TouchpadTutorialActivity. */
- @Binds
- @IntoMap
- @ClassKey(TouchpadTutorialActivity.class)
- public abstract Activity bindTouchpadTutorialActivity(TouchpadTutorialActivity activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 9ae63a1..3273111 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -77,6 +77,7 @@
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
import com.android.systemui.toast.ToastModule;
+import com.android.systemui.touchpad.tutorial.TouchpadKeyboardTutorialModule;
import com.android.systemui.unfold.SysUIUnfoldStartableModule;
import com.android.systemui.unfold.UnfoldTransitionModule;
import com.android.systemui.util.kotlin.SysUICoroutinesModule;
@@ -141,6 +142,7 @@
SysUIUnfoldStartableModule.class,
UnfoldTransitionModule.Startables.class,
ToastModule.class,
+ TouchpadKeyboardTutorialModule.class,
VolumeModule.class,
WallpaperModule.class,
ShortcutHelperModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 83fa001..9823985 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -56,6 +56,7 @@
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.communal.shared.log.CommunalUiEvent;
import com.android.systemui.communal.shared.model.CommunalScenes;
import com.android.systemui.complication.Complication;
import com.android.systemui.complication.dagger.ComplicationComponent;
@@ -407,6 +408,7 @@
@Override
public void onWakeRequested() {
+ mUiEventLogger.log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START);
mCommunalInteractor.changeScene(CommunalScenes.Communal, null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index 04fda33..ee7b6f5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -20,6 +20,7 @@
import android.graphics.Rect;
import android.graphics.Region;
+import android.util.LayoutDirection;
import android.view.GestureDetector;
import android.view.MotionEvent;
@@ -27,6 +28,7 @@
import androidx.lifecycle.Lifecycle;
import com.android.systemui.ambient.touch.TouchHandler;
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -43,30 +45,42 @@
private final Optional<CentralSurfaces> mCentralSurfaces;
private final Lifecycle mLifecycle;
private final CommunalInteractor mCommunalInteractor;
+
+ private final ConfigurationInteractor mConfigurationInteractor;
private Boolean mIsEnabled = false;
+ private int mLayoutDirection = LayoutDirection.LTR;
+
@VisibleForTesting
- final Consumer<Boolean> mIsCommunalAvailableCallback =
- isAvailable -> {
- setIsEnabled(isAvailable);
- };
+ final Consumer<Boolean> mIsCommunalAvailableCallback = isAvailable -> setIsEnabled(isAvailable);
+
+ @VisibleForTesting
+ final Consumer<Integer> mLayoutDirectionCallback = direction -> mLayoutDirection = direction;
@Inject
public CommunalTouchHandler(
Optional<CentralSurfaces> centralSurfaces,
@Named(CommunalTouchModule.COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth,
CommunalInteractor communalInteractor,
+ ConfigurationInteractor configurationInteractor,
Lifecycle lifecycle) {
mInitiationWidth = initiationWidth;
mCentralSurfaces = centralSurfaces;
mLifecycle = lifecycle;
mCommunalInteractor = communalInteractor;
+ mConfigurationInteractor = configurationInteractor;
collectFlow(
mLifecycle,
mCommunalInteractor.isCommunalAvailable(),
mIsCommunalAvailableCallback
);
+
+ collectFlow(
+ mLifecycle,
+ mConfigurationInteractor.getLayoutDirection(),
+ mLayoutDirectionCallback
+ );
}
@Override
@@ -90,7 +104,15 @@
@Override
public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
final Rect outBounds = new Rect(bounds);
- outBounds.inset(outBounds.width() - mInitiationWidth, 0, 0, 0);
+ final int inset = outBounds.width() - mInitiationWidth;
+
+ // Touch initiation area is defined in terms of LTR. The insets must be flipped for RTL
+ if (mLayoutDirection == LayoutDirection.LTR) {
+ outBounds.inset(inset, 0, 0, 0);
+ } else {
+ outBounds.inset(0, 0, inset, 0);
+ }
+
region.op(outBounds, Region.Op.UNION);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
index 7d11d32..303f916 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
@@ -17,6 +17,7 @@
package com.android.systemui.flags;
import static com.android.systemui.Flags.exampleFlag;
+import static com.android.systemui.Flags.classicFlagsMultiUser;
import static com.android.systemui.Flags.sysuiTeamfood;
import static com.android.systemui.flags.FlagManager.ACTION_GET_FLAGS;
import static com.android.systemui.flags.FlagManager.ACTION_SET_FLAG;
@@ -41,6 +42,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.util.settings.GlobalSettings;
import java.io.PrintWriter;
@@ -125,9 +127,14 @@
@Main Resources resources,
ServerFlagReader serverFlagReader,
@Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
- Restarter restarter) {
+ Restarter restarter,
+ UserContextProvider userContextProvider) {
mFlagManager = flagManager;
- mContext = context;
+ if (classicFlagsMultiUser()) {
+ mContext = userContextProvider.createCurrentUserContext(context);
+ } else {
+ mContext = context;
+ }
mGlobalSettings = globalSettings;
mResources = resources;
mSystemProperties = systemProperties;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d0beb7a..8990505 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -292,15 +292,6 @@
val WM_ENABLE_SHELL_TRANSITIONS =
sysPropBooleanFlag("persist.wm.debug.shell_transit", default = true)
- // TODO(b/254513207): Tracking Bug
- @Keep
- @JvmField
- val WM_ENABLE_PARTIAL_SCREEN_SHARING =
- releasedFlag(
- name = "enable_record_task_content",
- namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- )
-
// TODO(b/256873975): Tracking Bug
@JvmField
@Keep
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index 491c73d..27c20aa 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -26,7 +26,6 @@
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
-import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -48,7 +47,6 @@
constructor(
private val vibratorHelper: VibratorHelper?,
private val keyguardStateController: KeyguardStateController,
- private val falsingManager: FalsingManager,
) {
var effectDuration = 0
@@ -195,11 +193,8 @@
@VisibleForTesting
fun getStateForClick(): State {
val isTileUnavailable = qsTile?.state?.state == Tile.STATE_UNAVAILABLE
- val isFalseTapWhileLocked =
- !keyguardStateController.isUnlocked &&
- falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
val handlesLongClick = qsTile?.state?.handlesLongClick == true
- return if (isTileUnavailable || isFalseTapWhileLocked || !handlesLongClick) {
+ return if (isTileUnavailable || !handlesLongClick || keyguardStateController.isShowing) {
// The click event will not perform an action that resets the state. Therefore, this is
// the last opportunity to reset the state back to IDLE.
State.IDLE
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
index bdc18b3..0e19d87 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
@@ -22,6 +22,8 @@
* @see android.view.inputmethod.InputMethodInfo
*/
data class InputMethodModel(
+ /** A unique ID for the user associated with this input method. */
+ val userId: Int,
/** A unique ID for this input method. */
val imeId: String,
/** The subtypes of this IME (may be empty). */
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
index 5f316c4..c6fdc32 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
@@ -18,7 +18,6 @@
import android.annotation.SuppressLint
import android.os.UserHandle
-import android.view.inputmethod.InputMethodInfo
import android.view.inputmethod.InputMethodManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -34,18 +33,27 @@
/** Provides access to input-method related application state in the bouncer. */
interface InputMethodRepository {
+
/**
* Creates and returns a new `Flow` of installed input methods that are enabled for the
* specified user.
*
+ * @param user The user to query.
* @param fetchSubtypes Whether to fetch the IME Subtypes as well (requires an additional IPC
* call for each IME, avoid if not needed).
* @see InputMethodManager.getEnabledInputMethodListAsUser
*/
- suspend fun enabledInputMethods(userId: Int, fetchSubtypes: Boolean): Flow<InputMethodModel>
+ suspend fun enabledInputMethods(
+ user: UserHandle,
+ fetchSubtypes: Boolean,
+ ): Flow<InputMethodModel>
- /** Returns enabled subtypes for the currently selected input method. */
- suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype>
+ /**
+ * Returns enabled subtypes for the currently selected input method.
+ *
+ * @param user The user to query.
+ */
+ suspend fun selectedInputMethodSubtypes(user: UserHandle): List<InputMethodModel.Subtype>
/**
* Shows the system's input method picker dialog.
@@ -67,20 +75,22 @@
) : InputMethodRepository {
override suspend fun enabledInputMethods(
- userId: Int,
+ user: UserHandle,
fetchSubtypes: Boolean
): Flow<InputMethodModel> {
return withContext(backgroundDispatcher) {
- inputMethodManager.getEnabledInputMethodListAsUser(UserHandle.of(userId))
+ inputMethodManager.getEnabledInputMethodListAsUser(user)
}
.asFlow()
.map { inputMethodInfo ->
InputMethodModel(
+ userId = user.identifier,
imeId = inputMethodInfo.id,
subtypes =
if (fetchSubtypes) {
enabledInputMethodSubtypes(
- inputMethodInfo,
+ user = user,
+ imeId = inputMethodInfo.id,
allowsImplicitlyEnabledSubtypes = true
)
} else {
@@ -90,11 +100,19 @@
}
}
- override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> {
- return enabledInputMethodSubtypes(
- inputMethodInfo = null, // Fetch subtypes for the currently-selected IME.
- allowsImplicitlyEnabledSubtypes = false
- )
+ override suspend fun selectedInputMethodSubtypes(
+ user: UserHandle,
+ ): List<InputMethodModel.Subtype> {
+ val selectedIme = inputMethodManager.getCurrentInputMethodInfoAsUser(user)
+ return if (selectedIme == null) {
+ emptyList()
+ } else {
+ enabledInputMethodSubtypes(
+ user = user,
+ imeId = selectedIme.id,
+ allowsImplicitlyEnabledSubtypes = false
+ )
+ }
}
@SuppressLint("MissingPermission")
@@ -107,21 +125,23 @@
/**
* Returns a list of enabled input method subtypes for the specified input method info.
*
- * @param inputMethodInfo The [InputMethodInfo] whose subtypes list will be returned. If `null`,
- * returns enabled subtypes for the currently selected [InputMethodInfo].
+ * @param user The user to query.
+ * @param imeId The ID of the input method whose subtypes list will be returned.
* @param allowsImplicitlyEnabledSubtypes Whether to allow to return the implicitly enabled
* subtypes. If an input method info doesn't have enabled subtypes, the framework will
* implicitly enable subtypes according to the current system language.
- * @see InputMethodManager.getEnabledInputMethodSubtypeList
+ * @see InputMethodManager.getEnabledInputMethodSubtypeListAsUser
*/
private suspend fun enabledInputMethodSubtypes(
- inputMethodInfo: InputMethodInfo?,
+ user: UserHandle,
+ imeId: String,
allowsImplicitlyEnabledSubtypes: Boolean
): List<InputMethodModel.Subtype> {
return withContext(backgroundDispatcher) {
- inputMethodManager.getEnabledInputMethodSubtypeList(
- inputMethodInfo,
- allowsImplicitlyEnabledSubtypes
+ inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+ imeId,
+ allowsImplicitlyEnabledSubtypes,
+ user
)
}
.map {
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
index c54aa7f..d3ef178 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.inputmethod.domain.interactor
+import android.os.UserHandle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.inputmethod.data.repository.InputMethodRepository
import javax.inject.Inject
@@ -36,14 +37,16 @@
* Method adapted from `com.android.inputmethod.latin.Utils`.
*/
suspend fun hasMultipleEnabledImesOrSubtypes(userId: Int): Boolean {
+ val user = UserHandle.of(userId)
// Count IMEs that either have no subtypes, or have at least one non-auxiliary subtype.
val matchingInputMethods =
repository
- .enabledInputMethods(userId, fetchSubtypes = true)
+ .enabledInputMethods(user, fetchSubtypes = true)
.filter { ime -> ime.subtypes.isEmpty() || ime.subtypes.any { !it.isAuxiliary } }
.take(2) // Short-circuit if we find at least 2 matching IMEs.
- return matchingInputMethods.count() > 1 || repository.selectedInputMethodSubtypes().size > 1
+ return matchingInputMethods.count() > 1 ||
+ repository.selectedInputMethodSubtypes(user).size > 1
}
/** Shows the system's input method picker dialog. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index be64ff6..af755d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -384,16 +384,7 @@
if (index > 0) {
HorizontalDivider()
}
- ShortcutSinglePane(searchQuery, shortcut)
- }
-}
-
-@Composable
-private fun ShortcutSinglePane(searchQuery: String, shortcut: Shortcut) {
- Column(Modifier.padding(vertical = 24.dp)) {
- ShortcutDescriptionText(searchQuery = searchQuery, shortcut = shortcut)
- Spacer(modifier = Modifier.height(12.dp))
- ShortcutKeyCombinations(shortcut = shortcut)
+ ShortcutView(Modifier.padding(vertical = 24.dp), searchQuery, shortcut)
}
}
@@ -421,7 +412,7 @@
onCategoryClicked = { onCategorySelected(it.type) }
)
Spacer(modifier = Modifier.width(24.dp))
- EndSidePanel(searchQuery, Modifier.fillMaxSize(), selectedCategory)
+ EndSidePanel(searchQuery, Modifier.fillMaxSize().padding(top = 8.dp), selectedCategory)
}
}
}
@@ -447,14 +438,14 @@
shape = RoundedCornerShape(28.dp),
color = MaterialTheme.colorScheme.surfaceBright
) {
- Column(Modifier.padding(horizontal = 32.dp, vertical = 24.dp)) {
+ Column(Modifier.padding(24.dp)) {
SubCategoryTitle(subCategory.label)
- Spacer(Modifier.height(24.dp))
+ Spacer(Modifier.height(8.dp))
subCategory.shortcuts.fastForEachIndexed { index, shortcut ->
if (index > 0) {
HorizontalDivider()
}
- ShortcutViewDualPane(searchQuery, shortcut)
+ ShortcutView(Modifier.padding(vertical = 16.dp), searchQuery, shortcut)
}
}
}
@@ -470,17 +461,17 @@
}
@Composable
-private fun ShortcutViewDualPane(searchQuery: String, shortcut: Shortcut) {
- Row(Modifier.padding(vertical = 16.dp)) {
+private fun ShortcutView(modifier: Modifier, searchQuery: String, shortcut: Shortcut) {
+ Row(modifier) {
Row(
- modifier = Modifier.width(160.dp).align(Alignment.CenterVertically),
+ modifier = Modifier.width(128.dp).align(Alignment.CenterVertically),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
if (shortcut.icon != null) {
ShortcutIcon(
shortcut.icon,
- modifier = Modifier.size(36.dp),
+ modifier = Modifier.size(24.dp),
)
}
ShortcutDescriptionText(
@@ -520,7 +511,11 @@
modifier: Modifier = Modifier,
shortcut: Shortcut,
) {
- FlowRow(modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) {
+ FlowRow(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ horizontalArrangement = Arrangement.End
+ ) {
shortcut.commands.forEachIndexed { index, command ->
if (index > 0) {
ShortcutOrSeparator(spacing = 16.dp)
@@ -641,7 +636,7 @@
) {
Column(modifier) {
ShortcutsSearchBar(onSearchQueryChanged)
- Spacer(modifier = Modifier.heightIn(16.dp))
+ Spacer(modifier = Modifier.heightIn(8.dp))
CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked)
Spacer(modifier = Modifier.weight(1f))
KeyboardSettings(onKeyboardSettingsClicked)
@@ -678,7 +673,7 @@
Surface(
selected = selected,
onClick = onClick,
- modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 72.dp).fillMaxWidth(),
+ modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 64.dp).fillMaxWidth(),
shape = RoundedCornerShape(28.dp),
color = colors.containerColor(selected).value,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
index b8d0c23..2039743 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
@@ -26,8 +26,10 @@
import androidx.activity.BackEventCompat
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalContext
import androidx.core.view.updatePadding
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.flowWithLifecycle
@@ -36,6 +38,7 @@
import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutHelper
import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
import com.android.systemui.res.R
+import com.android.systemui.settings.UserTracker
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
@@ -49,6 +52,7 @@
class ShortcutHelperActivity
@Inject
constructor(
+ private val userTracker: UserTracker,
private val viewModel: ShortcutHelperViewModel,
) : ComponentActivity() {
@@ -79,13 +83,16 @@
private fun setUpComposeView() {
requireViewById<ComposeView>(R.id.shortcut_helper_compose_container).apply {
setContent {
- PlatformTheme {
- val shortcutsUiState by viewModel.shortcutsUiState.collectAsStateWithLifecycle()
- ShortcutHelper(
- shortcutsUiState = shortcutsUiState,
- onKeyboardSettingsClicked = ::onKeyboardSettingsClicked,
- onSearchQueryChanged = { viewModel.onSearchQueryChanged(it) },
- )
+ CompositionLocalProvider(LocalContext provides userTracker.userContext) {
+ PlatformTheme {
+ val shortcutsUiState by
+ viewModel.shortcutsUiState.collectAsStateWithLifecycle()
+ ShortcutHelper(
+ shortcutsUiState = shortcutsUiState,
+ onKeyboardSettingsClicked = ::onKeyboardSettingsClicked,
+ onSearchQueryChanged = { viewModel.onSearchQueryChanged(it) },
+ )
+ }
}
}
}
@@ -93,7 +100,10 @@
private fun onKeyboardSettingsClicked() {
try {
- startActivity(Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS))
+ startActivityAsUser(
+ Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS),
+ userTracker.userHandle
+ )
} catch (e: ActivityNotFoundException) {
// From the Settings docs: In some cases, a matching Activity may not exist, so ensure
// you safeguard against this.
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index e64cc80..19b46e3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyboard.shortcut.ui.viewmodel
+import android.app.role.RoleManager
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
@@ -25,6 +26,7 @@
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
+import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -35,10 +37,13 @@
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
class ShortcutHelperViewModel
@Inject
constructor(
+ private val roleManager: RoleManager,
+ private val userTracker: UserTracker,
@Background private val backgroundScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val stateInteractor: ShortcutHelperStateInteractor,
@@ -72,13 +77,22 @@
initialValue = ShortcutsUiState.Inactive
)
- private fun getDefaultSelectedCategory(
+ private suspend fun getDefaultSelectedCategory(
categories: List<ShortcutCategory>
): ShortcutCategoryType? {
- val currentAppShortcuts = categories.firstOrNull { it.type is CurrentApp }
+ val currentAppShortcuts =
+ categories.firstOrNull { it.type is CurrentApp && !isAppLauncher(it.type.packageName) }
return currentAppShortcuts?.type ?: categories.firstOrNull()?.type
}
+ private suspend fun isAppLauncher(packageName: String): Boolean {
+ return withContext(backgroundDispatcher) {
+ roleManager
+ .getRoleHoldersAsUser(RoleManager.ROLE_HOME, userTracker.userHandle)
+ .firstOrNull() == packageName
+ }
+ }
+
private fun filterCategoriesBySearchQuery(
query: String,
categories: List<ShortcutCategory>
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index d1a8463..2f41c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -62,21 +62,21 @@
const val TAG = "KeyguardUnlock"
/**
- * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating
- * in during keyguard exit.
+ * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating in
+ * during keyguard exit.
*/
const val SURFACE_BEHIND_START_SCALE_FACTOR = 0.95f
/**
- * How much to translate the surface behind the keyguard at the beginning of the exit animation,
- * in terms of percentage of the surface's height.
+ * How much to translate the surface behind the keyguard at the beginning of the exit animation, in
+ * terms of percentage of the surface's height.
*/
const val SURFACE_BEHIND_START_TRANSLATION_Y = 0.05f
/**
- * Y coordinate of the pivot point for the scale effect on the surface behind the keyguard. This
- * is expressed as percentage of the surface's height, so 0.66f means the surface will scale up
- * from the point at (width / 2, height * 0.66).
+ * Y coordinate of the pivot point for the scale effect on the surface behind the keyguard. This is
+ * expressed as percentage of the surface's height, so 0.66f means the surface will scale up from
+ * the point at (width / 2, height * 0.66).
*/
const val SURFACE_BEHIND_SCALE_PIVOT_Y = 0.66f
@@ -155,19 +155,20 @@
* [notifyStartSurfaceBehindRemoteAnimation] by [KeyguardViewMediator].
*/
@SysUISingleton
-class KeyguardUnlockAnimationController @Inject constructor(
- private val windowManager: WindowManager,
- @Main private val resources: Resources,
- private val keyguardStateController: KeyguardStateController,
- private val
- keyguardViewMediator: Lazy<KeyguardViewMediator>,
- private val keyguardViewController: KeyguardViewController,
- private val featureFlags: FeatureFlags,
- private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
- private val statusBarStateController: SysuiStatusBarStateController,
- private val notificationShadeWindowController: NotificationShadeWindowController,
- private val powerManager: PowerManager,
- private val wallpaperManager: WallpaperManager,
+open class KeyguardUnlockAnimationController
+@Inject
+constructor(
+ private val windowManager: WindowManager,
+ @Main private val resources: Resources,
+ private val keyguardStateController: KeyguardStateController,
+ private val keyguardViewMediator: Lazy<KeyguardViewMediator>,
+ private val keyguardViewController: KeyguardViewController,
+ private val featureFlags: FeatureFlags,
+ private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
+ private val statusBarStateController: SysuiStatusBarStateController,
+ private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val powerManager: PowerManager,
+ private val wallpaperManager: WallpaperManager,
) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
interface KeyguardUnlockAnimationListener {
@@ -221,8 +222,8 @@
var playingCannedUnlockAnimation = false
/**
- * Whether we reached the swipe gesture threshold to dismiss keyguard, or restore it, once
- * and should ignore any future changes to the dismiss amount before the animation finishes.
+ * Whether we reached the swipe gesture threshold to dismiss keyguard, or restore it, once and
+ * should ignore any future changes to the dismiss amount before the animation finishes.
*/
var dismissAmountThresholdsReached = false
@@ -235,9 +236,7 @@
*/
private var launcherUnlockController: ILauncherUnlockAnimationController? = null
- /**
- * Fully qualified class name of the launcher activity
- */
+ /** Fully qualified class name of the launcher activity */
private var launcherActivityClass: String? = null
private val listeners = ArrayList<KeyguardUnlockAnimationListener>()
@@ -248,8 +247,8 @@
* transition, but that's okay!
*/
override fun setLauncherUnlockController(
- activityClass: String,
- callback: ILauncherUnlockAnimationController?
+ activityClass: String,
+ callback: ILauncherUnlockAnimationController?
) {
launcherActivityClass = activityClass
launcherUnlockController = callback
@@ -274,8 +273,7 @@
* If we're unlocking via biometrics, PIN entry, or from clicking a notification, a canned
* animation is started in [playCannedUnlockAnimation].
*/
- @VisibleForTesting
- var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
+ @VisibleForTesting var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
private var surfaceBehindRemoteAnimationTargets: Array<RemoteAnimationTarget>? = null
private var openingWallpaperTargets: Array<RemoteAnimationTarget>? = null
private var closingWallpaperTargets: Array<RemoteAnimationTarget>? = null
@@ -291,8 +289,7 @@
*/
private var surfaceBehindAlpha = 1f
- @VisibleForTesting
- var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
+ @VisibleForTesting var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
var wallpaperCannedUnlockAnimator = ValueAnimator.ofFloat(0f, 1f)
@@ -310,8 +307,7 @@
* Animator that animates in the surface behind the keyguard. This is used to play a canned
* animation on the surface, if we're not doing a swipe gesture.
*/
- @VisibleForTesting
- val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f)
+ @VisibleForTesting val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f)
/** Rounded corner radius to apply to the surface behind the keyguard. */
private var roundedCornerRadius = 0f
@@ -322,8 +318,7 @@
* window like any other app. This can be true while [willUnlockWithSmartspaceTransition] is
* false, if the smartspace is not available or was not ready in time.
*/
- @VisibleForTesting
- var willUnlockWithInWindowLauncherAnimations: Boolean = false
+ @VisibleForTesting var willUnlockWithInWindowLauncherAnimations: Boolean = false
/**
* Whether we called [ILauncherUnlockAnimationController.prepareForUnlock], but have not yet
@@ -353,49 +348,64 @@
surfaceBehindAlpha = valueAnimator.animatedValue as Float
updateSurfaceBehindAppearAmount()
}
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- // If we animated the surface alpha to 0f, it means we cancelled a swipe to
- // dismiss. In this case, we should ask the KeyguardViewMediator to end the
- // remote animation to hide the surface behind the keyguard, but should *not*
- // call onKeyguardExitRemoteAnimationFinished since that will hide the keyguard
- // and unlock the device as well as hiding the surface.
- if (surfaceBehindAlpha == 0f) {
- Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd")
- surfaceBehindRemoteAnimationTargets = null
- openingWallpaperTargets = null
- closingWallpaperTargets = null
- keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
- } else {
- Log.d(TAG, "skip finishSurfaceBehindRemoteAnimation" +
- " surfaceBehindAlpha=$surfaceBehindAlpha")
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ // If we animated the surface alpha to 0f, it means we cancelled a swipe to
+ // dismiss. In this case, we should ask the KeyguardViewMediator to end the
+ // remote animation to hide the surface behind the keyguard, but should
+ // *not* call onKeyguardExitRemoteAnimationFinished since that will hide the
+ // keyguard and unlock the device as well as hiding the surface.
+ if (surfaceBehindAlpha == 0f) {
+ Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd")
+ surfaceBehindRemoteAnimationTargets = null
+ openingWallpaperTargets = null
+ closingWallpaperTargets = null
+ keyguardViewMediator
+ .get()
+ .finishSurfaceBehindRemoteAnimation(false /* cancelled */)
+ } else {
+ Log.d(
+ TAG,
+ "skip finishSurfaceBehindRemoteAnimation" +
+ " surfaceBehindAlpha=$surfaceBehindAlpha"
+ )
+ }
}
}
- })
+ )
}
with(wallpaperCannedUnlockAnimator) {
- duration = if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
- else LAUNCHER_ICONS_ANIMATION_DURATION_MS
- interpolator = if (fasterUnlockTransition()) Interpolators.LINEAR
- else Interpolators.ALPHA_OUT
+ duration =
+ if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
+ else LAUNCHER_ICONS_ANIMATION_DURATION_MS
+ interpolator =
+ if (fasterUnlockTransition()) Interpolators.LINEAR else Interpolators.ALPHA_OUT
addUpdateListener { valueAnimator: ValueAnimator ->
setWallpaperAppearAmount(
- valueAnimator.animatedValue as Float, openingWallpaperTargets)
+ valueAnimator.animatedValue as Float,
+ openingWallpaperTargets
+ )
}
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator) {
- super.onAnimationStart(animation)
- Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ super.onAnimationStart(animation)
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ Log.d(TAG, "wallpaperCannedUnlockAnimator#onAnimationEnd")
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
+ false /* cancelled */
+ )
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+ }
}
- override fun onAnimationEnd(animation: Animator) {
- Log.d(TAG, "wallpaperCannedUnlockAnimator#onAnimationEnd")
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
- Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
- }
- })
+ )
}
if (fasterUnlockTransition()) {
@@ -405,7 +415,9 @@
interpolator = Interpolators.LINEAR
addUpdateListener { valueAnimator: ValueAnimator ->
setWallpaperAppearAmount(
- valueAnimator.animatedValue as Float, closingWallpaperTargets)
+ valueAnimator.animatedValue as Float,
+ closingWallpaperTargets
+ )
}
}
}
@@ -418,15 +430,19 @@
surfaceBehindAlpha = valueAnimator.animatedValue as Float
setSurfaceBehindAppearAmount(valueAnimator.animatedValue as Float)
}
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- Log.d(TAG, "surfaceBehindEntryAnimator#onAnimationEnd")
- playingCannedUnlockAnimation = false
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */
- )
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ Log.d(TAG, "surfaceBehindEntryAnimator#onAnimationEnd")
+ playingCannedUnlockAnimation = false
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
+ false /* cancelled */
+ )
+ }
}
- })
+ )
}
// Listen for changes in the dismiss amount.
@@ -436,9 +452,7 @@
resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
}
- /**
- * Add a listener to be notified of various stages of the unlock animation.
- */
+ /** Add a listener to be notified of various stages of the unlock animation. */
fun addKeyguardUnlockAnimationListener(listener: KeyguardUnlockAnimationListener) {
listeners.add(listener)
}
@@ -454,11 +468,11 @@
fun canPerformInWindowLauncherAnimations(): Boolean {
// TODO(b/278086361): Refactor in-window animations.
return !KeyguardWmStateRefactor.isEnabled &&
- isSupportedLauncherUnderneath() &&
- // If the launcher is underneath, but we're about to launch an activity, don't do
- // the animations since they won't be visible.
- !notificationShadeWindowController.isLaunchingActivity &&
- launcherUnlockController != null
+ isSupportedLauncherUnderneath() &&
+ // If the launcher is underneath, but we're about to launch an activity, don't do
+ // the animations since they won't be visible.
+ !notificationShadeWindowController.isLaunchingActivity &&
+ launcherUnlockController != null
}
/**
@@ -469,8 +483,11 @@
private fun logInWindowAnimationConditions() {
Log.wtf(TAG, "canPerformInWindowLauncherAnimations expected all of these to be true: ")
Log.wtf(TAG, " isNexusLauncherUnderneath: ${isSupportedLauncherUnderneath()}")
- Log.wtf(TAG, " !notificationShadeWindowController.isLaunchingActivity: " +
- "${!notificationShadeWindowController.isLaunchingActivity}")
+ Log.wtf(
+ TAG,
+ " !notificationShadeWindowController.isLaunchingActivity: " +
+ "${!notificationShadeWindowController.isLaunchingActivity}"
+ )
Log.wtf(TAG, " launcherUnlockController != null: ${launcherUnlockController != null}")
Log.wtf(TAG, " !isFoldable(context): ${!isFoldable(resources)}")
}
@@ -480,8 +497,10 @@
* changed.
*/
override fun onKeyguardGoingAwayChanged() {
- if (keyguardStateController.isKeyguardGoingAway &&
- !statusBarStateController.leaveOpenOnKeyguardHide()) {
+ if (
+ keyguardStateController.isKeyguardGoingAway &&
+ !statusBarStateController.leaveOpenOnKeyguardHide()
+ ) {
prepareForInWindowLauncherAnimations()
}
@@ -489,16 +508,22 @@
// make sure that we've left the launcher at 100% unlocked. This is a fail-safe to prevent
// against "tiny launcher" and similar states where the launcher is left in the prepared to
// animate state.
- if (!keyguardStateController.isKeyguardGoingAway &&
- willUnlockWithInWindowLauncherAnimations) {
+ if (
+ !keyguardStateController.isKeyguardGoingAway && willUnlockWithInWindowLauncherAnimations
+ ) {
try {
- launcherUnlockController?.setUnlockAmount(1f,
- biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */)
+ launcherUnlockController?.setUnlockAmount(
+ 1f,
+ biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */
+ )
} catch (e: DeadObjectException) {
- Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null in " +
+ Log.e(
+ TAG,
+ "launcherUnlockAnimationController was dead, but non-null in " +
"onKeyguardGoingAwayChanged(). Catching exception as this should mean " +
"Launcher is in the process of being destroyed, but the IPC to System UI " +
- "telling us hasn't arrived yet.")
+ "telling us hasn't arrived yet."
+ )
}
}
}
@@ -525,22 +550,26 @@
// Grab the bounds of our lockscreen smartspace and send them to launcher so they can
// position their smartspace there initially, then animate it to its resting position.
if (willUnlockWithSmartspaceTransition) {
- lockscreenSmartspaceBounds = Rect().apply {
- lockscreenSmartspace!!.getBoundsOnScreen(this)
+ lockscreenSmartspaceBounds =
+ Rect().apply {
+ lockscreenSmartspace!!.getBoundsOnScreen(this)
- // The smartspace container on the lockscreen has left and top padding to align it
- // with other lockscreen content. This padding is inside the bounds on screen, so
- // add it to those bounds so that the padding-less launcher smartspace is properly
- // aligned.
- offset(lockscreenSmartspace!!.paddingLeft, lockscreenSmartspace!!.paddingTop)
+ // The smartspace container on the lockscreen has left and top padding to align
+ // it with other lockscreen content. This padding is inside the bounds on
+ // screen, so add it to those bounds so that the padding-less launcher
+ // smartspace is properly aligned.
+ offset(lockscreenSmartspace!!.paddingLeft, lockscreenSmartspace!!.paddingTop)
- // Also offset by the current card's top padding, if it has any. This allows us to
- // align the tops of the lockscreen/launcher smartspace cards. Some cards, such as
- // the three-line date/weather/alarm card, only have three lines on lockscreen but
- // two on launcher.
- offset(0, (lockscreenSmartspace
- as? BcSmartspaceDataPlugin.SmartspaceView)?.currentCardTopPadding ?: 0)
- }
+ // Also offset by the current card's top padding, if it has any. This allows us
+ // to align the tops of the lockscreen/launcher smartspace cards. Some cards,
+ // such as the three-line date/weather/alarm card, only have three lines on
+ // lockscreen but two on launcher.
+ offset(
+ 0,
+ (lockscreenSmartspace as? BcSmartspaceDataPlugin.SmartspaceView)
+ ?.currentCardTopPadding ?: 0
+ )
+ }
}
// Currently selected lockscreen smartspace page, or -1 if it's not available.
@@ -583,8 +612,8 @@
requestedShowSurfaceBehindKeyguard: Boolean
) {
if (surfaceTransactionApplier == null) {
- surfaceTransactionApplier = SyncRtSurfaceTransactionApplier(
- keyguardViewController.viewRootImpl.view)
+ surfaceTransactionApplier =
+ SyncRtSurfaceTransactionApplier(keyguardViewController.viewRootImpl.view)
}
surfaceBehindRemoteAnimationTargets = targets
@@ -603,8 +632,10 @@
// surface behind the keyguard to finish unlocking.
if (keyguardStateController.isFlingingToDismissKeyguard) {
playCannedUnlockAnimation()
- } else if (keyguardStateController.isDismissingFromSwipe &&
- willUnlockWithInWindowLauncherAnimations) {
+ } else if (
+ keyguardStateController.isDismissingFromSwipe &&
+ willUnlockWithInWindowLauncherAnimations
+ ) {
// If we're swiping to unlock to the Launcher, and can play in-window animations,
// make the launcher surface fully visible and play the in-window unlock animation
// on the launcher icons. System UI will remain locked, using the swipe-to-unlock
@@ -615,19 +646,23 @@
try {
launcherUnlockController?.playUnlockAnimation(
- true,
- unlockAnimationDurationMs() + cannedUnlockStartDelayMs(),
- 0 /* startDelay */)
+ true,
+ unlockAnimationDurationMs() + cannedUnlockStartDelayMs(),
+ 0 /* startDelay */
+ )
} catch (e: DeadObjectException) {
// Hello! If you are here investigating a bug where Launcher is blank (no icons)
// then the below assumption about Launcher's destruction was incorrect. This
// would mean prepareToUnlock was called (blanking Launcher in preparation for
// the beginning of the unlock animation), but then somehow we were unable to
// call playUnlockAnimation to animate the icons back in.
- Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null. " +
+ Log.e(
+ TAG,
+ "launcherUnlockAnimationController was dead, but non-null. " +
"Catching exception as this should mean Launcher is in the process " +
"of being destroyed, but the IPC to System UI telling us hasn't " +
- "arrived yet.")
+ "arrived yet."
+ )
}
launcherPreparedForUnlock = false
@@ -643,15 +678,18 @@
}
// Notify if waking from AOD only
- val isWakeAndUnlockNotFromDream = biometricUnlockControllerLazy.get().isWakeAndUnlock &&
- biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
+ val isWakeAndUnlockNotFromDream =
+ biometricUnlockControllerLazy.get().isWakeAndUnlock &&
+ biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
listeners.forEach {
it.onUnlockAnimationStarted(
playingCannedUnlockAnimation /* playingCannedAnimation */,
isWakeAndUnlockNotFromDream /* isWakeAndUnlockNotFromDream */,
cannedUnlockStartDelayMs() /* unlockStartDelay */,
- LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */) }
+ LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */
+ )
+ }
// Finish the keyguard remote animation if the dismiss amount has crossed the threshold.
// Check it here in case there is no more change to the dismiss amount after the last change
@@ -685,8 +723,9 @@
biometricUnlockControllerLazy.get().isWakeAndUnlock -> {
Log.d(TAG, "playCannedUnlockAnimation, isWakeAndUnlock")
setSurfaceBehindAppearAmount(1f)
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
// Otherwise, we're doing a normal full-window unlock. Start this animator, which will
@@ -698,8 +737,11 @@
}
if (launcherPreparedForUnlock && !willUnlockWithInWindowLauncherAnimations) {
- Log.wtf(TAG, "Launcher is prepared for unlock, so we should have started the " +
- "in-window animation, however we apparently did not.")
+ Log.wtf(
+ TAG,
+ "Launcher is prepared for unlock, so we should have started the " +
+ "in-window animation, however we apparently did not."
+ )
logInWindowAnimationConditions()
}
}
@@ -708,7 +750,6 @@
* Unlock to the launcher, using in-window animations, and the smartspace shared element
* transition if possible.
*/
-
@VisibleForTesting
fun unlockToLauncherWithInWindowAnimations() {
surfaceBehindAlpha = 1f
@@ -717,26 +758,32 @@
try {
// Begin the animation, waiting for the shade to animate out.
launcherUnlockController?.playUnlockAnimation(
- true /* unlocked */,
- LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
- cannedUnlockStartDelayMs() /* startDelay */)
+ true /* unlocked */,
+ LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
+ cannedUnlockStartDelayMs() /* startDelay */
+ )
} catch (e: DeadObjectException) {
// Hello! If you are here investigating a bug where Launcher is blank (no icons)
// then the below assumption about Launcher's destruction was incorrect. This
// would mean prepareToUnlock was called (blanking Launcher in preparation for
// the beginning of the unlock animation), but then somehow we were unable to
// call playUnlockAnimation to animate the icons back in.
- Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null. " +
+ Log.e(
+ TAG,
+ "launcherUnlockAnimationController was dead, but non-null. " +
"Catching exception as this should mean Launcher is in the process " +
"of being destroyed, but the IPC to System UI telling us hasn't " +
- "arrived yet.")
+ "arrived yet."
+ )
}
launcherPreparedForUnlock = false
// Now that the Launcher surface (with its smartspace positioned identically to ours) is
// visible, hide our smartspace.
- if (lockscreenSmartspace?.visibility == View.VISIBLE) {
+ if (
+ shouldPerformSmartspaceTransition() && lockscreenSmartspace?.visibility == View.VISIBLE
+ ) {
lockscreenSmartspace?.visibility = View.INVISIBLE
}
@@ -747,22 +794,31 @@
fadeOutWallpaper()
}
- handler.postDelayed({
- if (keyguardViewMediator.get().isShowingAndNotOccluded &&
- !keyguardStateController.isKeyguardGoingAway) {
- Log.e(TAG, "Finish keyguard exit animation delayed Runnable ran, but we are " +
- "showing and not going away.")
- return@postDelayed
- }
+ handler.postDelayed(
+ {
+ if (
+ keyguardViewMediator.get().isShowingAndNotOccluded &&
+ !keyguardStateController.isKeyguardGoingAway
+ ) {
+ Log.e(
+ TAG,
+ "Finish keyguard exit animation delayed Runnable ran, but we are " +
+ "showing and not going away."
+ )
+ return@postDelayed
+ }
- if (openingWallpaperTargets?.isNotEmpty() == true) {
- fadeInWallpaper()
- hideKeyguardViewAfterRemoteAnimation()
- } else {
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
- }
- }, cannedUnlockStartDelayMs())
+ if (openingWallpaperTargets?.isNotEmpty() == true) {
+ fadeInWallpaper()
+ hideKeyguardViewAfterRemoteAnimation()
+ } else {
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
+ }
+ },
+ cannedUnlockStartDelayMs()
+ )
}
/**
@@ -784,12 +840,14 @@
// interaction tight.
if (keyguardStateController.isFlingingToDismissKeyguard) {
setSurfaceBehindAppearAmount(keyguardStateController.dismissAmount)
- } else if (keyguardStateController.isDismissingFromSwipe ||
- keyguardStateController.isSnappingKeyguardBackAfterSwipe) {
+ } else if (
+ keyguardStateController.isDismissingFromSwipe ||
+ keyguardStateController.isSnappingKeyguardBackAfterSwipe
+ ) {
val totalSwipeDistanceToDismiss =
- (DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD)
+ (DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD)
val swipedDistanceSoFar: Float =
- keyguardStateController.dismissAmount - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD
+ keyguardStateController.dismissAmount - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD
val progress = swipedDistanceSoFar / totalSwipeDistanceToDismiss
setSurfaceBehindAppearAmount(progress)
}
@@ -801,10 +859,13 @@
// If the surface is visible or it's about to be, start updating its appearance to
// reflect the new dismiss amount.
- if ((keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
- keyguardViewMediator.get()
+ if (
+ (keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
+ keyguardViewMediator
+ .get()
.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) &&
- !playingCannedUnlockAnimation) {
+ !playingCannedUnlockAnimation
+ ) {
updateSurfaceBehindAppearAmount()
}
}
@@ -838,11 +899,15 @@
val dismissAmount = keyguardStateController.dismissAmount
- if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
- !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
+ if (
+ dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
+ !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()
+ ) {
keyguardViewMediator.get().showSurfaceBehindKeyguard()
- } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
- keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
+ } else if (
+ dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
+ keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()
+ ) {
// We're no longer past the threshold but we are showing the surface. Animate it
// out.
keyguardViewMediator.get().hideSurfaceBehindKeyguard()
@@ -868,22 +933,27 @@
}
// no-op if animation is not requested yet.
- if (!keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
- !keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) {
+ if (
+ !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
+ !keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe
+ ) {
return
}
val dismissAmount = keyguardStateController.dismissAmount
- if (dismissAmount >= 1f ||
+ if (
+ dismissAmount >= 1f ||
(keyguardStateController.isDismissingFromSwipe &&
- // Don't hide if we're flinging during a swipe, since we need to finish
- // animating it out. This will be called again after the fling ends.
- !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
- dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)) {
+ // Don't hide if we're flinging during a swipe, since we need to finish
+ // animating it out. This will be called again after the fling ends.
+ !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
+ dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)
+ ) {
setSurfaceBehindAppearAmount(1f)
dismissAmountThresholdsReached = true
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
}
@@ -894,51 +964,56 @@
* wallpapers, this transitions between the two wallpapers
*/
fun setSurfaceBehindAppearAmount(amount: Float, wallpapers: Boolean = true) {
- val animationAlpha = when {
- // If we're snapping the keyguard back, immediately begin fading it out.
- keyguardStateController.isSnappingKeyguardBackAfterSwipe -> amount
- // If the screen has turned back off, the unlock animation is going to be cancelled,
- // so set the surface alpha to 0f so it's no longer visible.
- !powerManager.isInteractive -> 0f
- else -> surfaceBehindAlpha
- }
+ val animationAlpha =
+ when {
+ // If we're snapping the keyguard back, immediately begin fading it out.
+ keyguardStateController.isSnappingKeyguardBackAfterSwipe -> amount
+ // If the screen has turned back off, the unlock animation is going to be cancelled,
+ // so set the surface alpha to 0f so it's no longer visible.
+ !powerManager.isInteractive -> 0f
+ else -> surfaceBehindAlpha
+ }
surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
if (!KeyguardWmStateRefactor.isEnabled) {
val surfaceHeight: Int =
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
- var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
- (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
- MathUtils.clamp(amount, 0f, 1f))
+ var scaleFactor =
+ (SURFACE_BEHIND_START_SCALE_FACTOR +
+ (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * MathUtils.clamp(amount, 0f, 1f))
// If we're dismissing via swipe to the Launcher, we'll play in-window scale
// animations, so don't also scale the window.
- if (keyguardStateController.isDismissingFromSwipe &&
- willUnlockWithInWindowLauncherAnimations) {
+ if (
+ keyguardStateController.isDismissingFromSwipe &&
+ willUnlockWithInWindowLauncherAnimations
+ ) {
scaleFactor = 1f
}
// Translate up from the bottom.
surfaceBehindMatrix.setTranslate(
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
- surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
+ surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
)
// Scale up from a point at the center-bottom of the surface.
surfaceBehindMatrix.postScale(
- scaleFactor,
- scaleFactor,
- keyguardViewController.viewRootImpl.width / 2f,
- surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
+ scaleFactor,
+ scaleFactor,
+ keyguardViewController.viewRootImpl.width / 2f,
+ surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
)
// SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
// unable to draw
val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
- if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
- sc?.isValid == true) {
+ if (
+ keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc?.isValid == true
+ ) {
with(SurfaceControl.Transaction()) {
setMatrix(sc, surfaceBehindMatrix, tmpFloat)
setCornerRadius(sc, roundedCornerRadius)
@@ -947,12 +1022,13 @@
}
} else {
applyParamsToSurface(
- SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- surfaceBehindRemoteAnimationTarget.leash)
- .withMatrix(surfaceBehindMatrix)
- .withCornerRadius(roundedCornerRadius)
- .withAlpha(animationAlpha)
- .build()
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+ surfaceBehindRemoteAnimationTarget.leash
+ )
+ .withMatrix(surfaceBehindMatrix)
+ .withCornerRadius(roundedCornerRadius)
+ .withAlpha(animationAlpha)
+ .build()
)
}
}
@@ -969,8 +1045,8 @@
val fadeOutStart = LOCK_WALLPAPER_FADE_OUT_START_DELAY / total
val fadeOutEnd = fadeOutStart + LOCK_WALLPAPER_FADE_OUT_DURATION / total
- val fadeOutAmount = ((amount - fadeOutStart) / (fadeOutEnd - fadeOutStart))
- .coerceIn(0f, 1f)
+ val fadeOutAmount =
+ ((amount - fadeOutStart) / (fadeOutEnd - fadeOutStart)).coerceIn(0f, 1f)
setWallpaperAppearAmount(fadeInAmount, openingWallpaperTargets)
setWallpaperAppearAmount(1 - fadeOutAmount, closingWallpaperTargets)
@@ -984,18 +1060,19 @@
// SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
// unable to draw
val sc: SurfaceControl? = wallpaper.leash
- if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
- sc?.isValid == true) {
+ if (
+ keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc?.isValid == true
+ ) {
with(SurfaceControl.Transaction()) {
setAlpha(sc, animationAlpha)
apply()
}
} else {
applyParamsToSurface(
- SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- wallpaper.leash)
- .withAlpha(animationAlpha)
- .build()
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(wallpaper.leash)
+ .withAlpha(animationAlpha)
+ .build()
)
}
}
@@ -1019,9 +1096,9 @@
}
if (!showKeyguard) {
- // Make sure we made the surface behind fully visible, just in case. It should already be
- // fully visible. The exit animation is finished, and we should not hold the leash anymore,
- // so forcing it to 1f.
+ // Make sure we made the surface behind fully visible, just in case. It should already
+ // be fully visible. The exit animation is finished, and we should not hold the leash
+ // anymore, so forcing it to 1f.
surfaceBehindAlpha = 1f
setSurfaceBehindAppearAmount(1f)
@@ -1061,13 +1138,16 @@
if (!KeyguardWmStateRefactor.isEnabled) {
keyguardViewController.hide(
- surfaceBehindRemoteAnimationStartTime,
- 0 /* fadeOutDuration */
+ surfaceBehindRemoteAnimationStartTime,
+ 0 /* fadeOutDuration */
)
}
} else {
- Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
- "showing. Ignoring...")
+ Log.i(
+ TAG,
+ "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
+ "showing. Ignoring..."
+ )
}
}
@@ -1099,7 +1179,8 @@
surfaceBehindAlphaAnimator.reverse()
}
- private fun shouldPerformSmartspaceTransition(): Boolean {
+ /** Note: declared open for ease of testing */
+ open fun shouldPerformSmartspaceTransition(): Boolean {
// Feature is disabled, so we don't want to.
if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) {
return false
@@ -1107,9 +1188,11 @@
// If our controllers are null, or we haven't received a smartspace state from Launcher yet,
// we will not be doing any smartspace transitions today.
- if (launcherUnlockController == null ||
- lockscreenSmartspace == null ||
- launcherSmartspaceState == null) {
+ if (
+ launcherUnlockController == null ||
+ lockscreenSmartspace == null ||
+ launcherSmartspaceState == null
+ ) {
return false
}
@@ -1135,8 +1218,10 @@
// element transition is if we're doing a biometric unlock. Otherwise, it means the bouncer
// is showing, and you can't see the lockscreen smartspace, so a shared element transition
// would not make sense.
- if (!keyguardStateController.canDismissLockScreen() &&
- !biometricUnlockControllerLazy.get().isBiometricUnlock) {
+ if (
+ !keyguardStateController.canDismissLockScreen() &&
+ !biometricUnlockControllerLazy.get().isBiometricUnlock
+ ) {
return false
}
@@ -1175,9 +1260,7 @@
return willUnlockWithSmartspaceTransition
}
- /**
- * Whether the RemoteAnimation on the app/launcher surface behind the keyguard is 'running'.
- */
+ /** Whether the RemoteAnimation on the app/launcher surface behind the keyguard is 'running'. */
fun isAnimatingBetweenKeyguardAndSurfaceBehind(): Boolean {
return keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehind
}
@@ -1196,39 +1279,38 @@
* in-window/shared element transitions!
*/
fun isSupportedLauncherUnderneath(): Boolean {
- return launcherActivityClass?.let { ActivityManagerWrapper.getInstance()
- .runningTask?.topActivity?.className?.equals(it) }
- ?: false
+ return launcherActivityClass?.let {
+ ActivityManagerWrapper.getInstance().runningTask?.topActivity?.className?.equals(it)
+ } ?: false
}
/**
- * Temporary method for b/298186160
- * TODO (b/298186160) replace references with the constant itself when flag is removed
+ * Temporary method for b/298186160 TODO (b/298186160) replace references with the constant
+ * itself when flag is removed
*/
private fun cannedUnlockStartDelayMs(): Long {
return if (fasterUnlockTransition()) CANNED_UNLOCK_START_DELAY
- else LEGACY_CANNED_UNLOCK_START_DELAY
+ else LEGACY_CANNED_UNLOCK_START_DELAY
}
/**
- * Temporary method for b/298186160
- * TODO (b/298186160) replace references with the constant itself when flag is removed
+ * Temporary method for b/298186160 TODO (b/298186160) replace references with the constant
+ * itself when flag is removed
*/
private fun unlockAnimationDurationMs(): Long {
return if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
- else LEGACY_UNLOCK_ANIMATION_DURATION_MS
+ else LEGACY_UNLOCK_ANIMATION_DURATION_MS
}
/**
- * Temporary method for b/298186160
- * TODO (b/298186160) replace references with the constant itself when flag is removed
+ * Temporary method for b/298186160 TODO (b/298186160) replace references with the constant
+ * itself when flag is removed
*/
private fun surfaceBehindFadeOutStartDelayMs(): Long {
return if (fasterUnlockTransition()) UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
- else LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
+ else LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
}
-
companion object {
fun isFoldable(resources: Resources): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e46a7cb..0b3d0f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1278,6 +1278,8 @@
initAlphaForAnimationTargets(wallpapers);
if (isDream) {
mDreamViewModel.get().startTransitionFromDream();
+ } else {
+ mCommunalTransitionViewModel.get().snapToCommunal();
}
mUnoccludeFinishedCallback = finishedCallback;
return;
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/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index ccce3bf..31236a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -25,6 +25,7 @@
import com.android.app.tracing.coroutines.withContext
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
@@ -80,7 +81,8 @@
private val featureFlags: FeatureFlags,
private val repository: Lazy<KeyguardQuickAffordanceRepository>,
private val launchAnimator: DialogTransitionAnimator,
- private val logger: KeyguardQuickAffordancesMetricsLogger,
+ private val logger: KeyguardQuickAffordancesLogger,
+ private val metricsLogger: KeyguardQuickAffordancesMetricsLogger,
private val devicePolicyManager: DevicePolicyManager,
private val dockManager: DockManager,
private val biometricSettingsRepository: BiometricSettingsRepository,
@@ -171,7 +173,8 @@
Log.e(TAG, "Affordance config with key of \"$configKey\" not found!")
return
}
- logger.logOnShortcutTriggered(slotId, configKey)
+ logger.logQuickAffordanceTriggered(decodedSlotId, decodedConfigKey)
+ metricsLogger.logOnShortcutTriggered(slotId, configKey)
when (val result = config.onTriggered(expandable)) {
is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity ->
@@ -223,7 +226,8 @@
affordanceIds = selections,
)
- logger.logOnShortcutSelected(slotId, affordanceId)
+ logger.logQuickAffordanceSelected(slotId, affordanceId)
+ metricsLogger.logOnShortcutSelected(slotId, affordanceId)
return true
}
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/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
index b387855..830ef3b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
@@ -46,6 +46,7 @@
onSingleTap: () -> Unit,
falsingManager: FalsingManager,
) {
+ view.contentDescription = view.resources.getString(R.string.accessibility_desc_lock_screen)
view.accessibilityHintLongPressAction =
AccessibilityNodeInfo.AccessibilityAction(
AccessibilityNodeInfoCompat.ACTION_LONG_CLICK,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index b9a79dc..162a0d2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -30,6 +30,7 @@
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.settingslib.Utils
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.view.LaunchableImageView
@@ -74,6 +75,7 @@
alpha: Flow<Float>,
falsingManager: FalsingManager?,
vibratorHelper: VibratorHelper?,
+ logger: KeyguardQuickAffordancesLogger,
messageDisplayer: (Int) -> Unit,
): Binding {
val button = view as ImageView
@@ -89,6 +91,7 @@
falsingManager = falsingManager,
messageDisplayer = messageDisplayer,
vibratorHelper = vibratorHelper,
+ logger = logger,
)
}
}
@@ -131,6 +134,7 @@
falsingManager: FalsingManager?,
messageDisplayer: (Int) -> Unit,
vibratorHelper: VibratorHelper?,
+ logger: KeyguardQuickAffordancesLogger,
) {
if (!viewModel.isVisible) {
view.isInvisible = true
@@ -228,6 +232,7 @@
shakeAnimator.start()
vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
+ logger.logQuickAffordanceTapped(viewModel.configKey)
}
view.onLongClickListener =
OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index bc5b7b9..6faca1e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -50,6 +50,7 @@
import com.android.internal.policy.SystemBarUtils
import com.android.keyguard.ClockEventController
import com.android.keyguard.KeyguardClockSwitch
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -147,6 +148,7 @@
private val defaultShortcutsSection: DefaultShortcutsSection,
private val keyguardClockInteractor: KeyguardClockInteractor,
private val keyguardClockViewModel: KeyguardClockViewModel,
+ private val quickAffordancesLogger: KeyguardQuickAffordancesLogger,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
@@ -462,6 +464,7 @@
alpha = flowOf(1f),
falsingManager = falsingManager,
vibratorHelper = vibratorHelper,
+ logger = quickAffordancesLogger,
) { message ->
indicationController.showTransientIndication(message)
}
@@ -476,6 +479,7 @@
alpha = flowOf(1f),
falsingManager = falsingManager,
vibratorHelper = vibratorHelper,
+ logger = quickAffordancesLogger,
) { message ->
indicationController.showTransientIndication(message)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index 1c63235..3e6d5da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -247,6 +247,7 @@
lp.height = ViewGroup.LayoutParams.MATCH_PARENT
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
bgView.layoutParams = lp
+ bgView.alpha = 0f
}
fun getIconState(icon: IconType, aod: Boolean): IntArray {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
index 2e96638..1ba830b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
@@ -25,6 +25,7 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
@@ -47,6 +48,7 @@
private val falsingManager: FalsingManager,
private val indicationController: KeyguardIndicationController,
private val vibratorHelper: VibratorHelper,
+ private val shortcutsLogger: KeyguardQuickAffordancesLogger,
) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
if (KeyguardBottomAreaRefactor.isEnabled) {
@@ -64,6 +66,7 @@
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
+ shortcutsLogger,
) {
indicationController.showTransientIndication(it)
}
@@ -74,6 +77,7 @@
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
+ shortcutsLogger,
) {
indicationController.showTransientIndication(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 9146c60..64c46db 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -26,6 +26,7 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
@@ -52,6 +53,7 @@
private val indicationController: KeyguardIndicationController,
private val vibratorHelper: VibratorHelper,
private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
+ private val shortcutsLogger: KeyguardQuickAffordancesLogger,
) : BaseShortcutSection() {
// Amount to increase the bottom margin by to avoid colliding with inset
@@ -86,6 +88,7 @@
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
+ shortcutsLogger,
) {
indicationController.showTransientIndication(it)
}
@@ -96,6 +99,7 @@
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
+ shortcutsLogger,
) {
indicationController.showTransientIndication(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
index 00aa102..ea8fe29 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.LayoutDirection
import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -54,7 +55,10 @@
val dreamOverlayTranslationX: Flow<Float> =
configurationInteractor
- .dimensionPixelSize(R.dimen.dreaming_to_hub_transition_dream_overlay_translation_x)
+ .directionalDimensionPixelSize(
+ LayoutDirection.LTR,
+ R.dimen.dreaming_to_hub_transition_dream_overlay_translation_x
+ )
.flatMapLatest { translatePx ->
transitionAnimation.sharedFlow(
duration = TO_GLANCEABLE_HUB_DURATION,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
index d594488..76d5a8d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.LayoutDirection
import com.android.app.animation.Interpolators
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -63,7 +64,10 @@
val dreamOverlayTranslationX: Flow<Float> =
configurationInteractor
- .dimensionPixelSize(R.dimen.hub_to_dreaming_transition_dream_overlay_translation_x)
+ .directionalDimensionPixelSize(
+ LayoutDirection.LTR,
+ R.dimen.hub_to_dreaming_transition_dream_overlay_translation_x
+ )
.flatMapLatest { translatePx: Int ->
transitionAnimation.sharedFlow(
duration = FROM_GLANCEABLE_HUB_DURATION,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index 046b95f..67b009e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.LayoutDirection
import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -72,7 +73,10 @@
val keyguardTranslationX: Flow<StateToValue> =
configurationInteractor
- .dimensionPixelSize(R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x)
+ .directionalDimensionPixelSize(
+ LayoutDirection.LTR,
+ R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x
+ )
.flatMapLatest { translatePx: Int ->
transitionAnimation.sharedFlowWithState(
duration = TO_LOCKSCREEN_DURATION,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 630dcca..15892e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -25,7 +25,6 @@
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
@@ -35,93 +34,70 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.util.kotlin.filterValuesNotNull
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the lockscreen scene. */
@SysUISingleton
class LockscreenSceneViewModel
@Inject
constructor(
- @Application applicationScope: CoroutineScope,
- deviceEntryInteractor: DeviceEntryInteractor,
- communalInteractor: CommunalInteractor,
- shadeInteractor: ShadeInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val communalInteractor: CommunalInteractor,
+ private val shadeInteractor: ShadeInteractor,
val touchHandling: KeyguardTouchHandlingViewModel,
val notifications: NotificationsPlaceholderViewModel,
) {
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- shadeInteractor.isShadeTouchable
- .flatMapLatest { isShadeTouchable ->
- if (!isShadeTouchable) {
- flowOf(emptyMap())
- } else {
- combine(
- deviceEntryInteractor.isUnlocked,
- communalInteractor.isCommunalAvailable,
- shadeInteractor.shadeMode,
- ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
- destinationScenes(
- isDeviceUnlocked = isDeviceUnlocked,
- isCommunalAvailable = isCommunalAvailable,
- shadeMode = shadeMode,
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ shadeInteractor.isShadeTouchable.flatMapLatest { isShadeTouchable ->
+ if (!isShadeTouchable) {
+ flowOf(emptyMap())
+ } else {
+ combine(
+ deviceEntryInteractor.isUnlocked,
+ communalInteractor.isCommunalAvailable,
+ shadeInteractor.shadeMode,
+ ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+ val notifShadeSceneKey =
+ UserActionResult(
+ toScene = SceneFamilies.NotifShade,
+ transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
)
- }
+
+ mapOf(
+ Swipe.Left to
+ UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
+ Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
+
+ // Swiping down from the top edge goes to QS (or shade if in split shade
+ // mode).
+ swipeDownFromTop(pointerCount = 1) to
+ if (shadeMode is ShadeMode.Single) {
+ UserActionResult(Scenes.QuickSettings)
+ } else {
+ notifShadeSceneKey
+ },
+
+ // TODO(b/338577208): Remove once we add Dual Shade invocation zones.
+ swipeDownFromTop(pointerCount = 2) to
+ UserActionResult(
+ toScene = SceneFamilies.QuickSettings,
+ transitionKey =
+ ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+ ),
+
+ // Swiping down, not from the edge, always navigates to the notif shade
+ // scene.
+ swipeDown(pointerCount = 1) to notifShadeSceneKey,
+ swipeDown(pointerCount = 2) to notifShadeSceneKey,
+ )
+ .filterValuesNotNull()
}
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- destinationScenes(
- isDeviceUnlocked = deviceEntryInteractor.isUnlocked.value,
- isCommunalAvailable = false,
- shadeMode = shadeInteractor.shadeMode.value,
- ),
- )
-
- private fun destinationScenes(
- isDeviceUnlocked: Boolean,
- isCommunalAvailable: Boolean,
- shadeMode: ShadeMode,
- ): Map<UserAction, UserActionResult> {
- val notifShadeSceneKey =
- UserActionResult(
- toScene = SceneFamilies.NotifShade,
- transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
- )
-
- return mapOf(
- Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
- Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
-
- // Swiping down from the top edge goes to QS (or shade if in split shade mode).
- swipeDownFromTop(pointerCount = 1) to
- if (shadeMode is ShadeMode.Single) {
- UserActionResult(Scenes.QuickSettings)
- } else {
- notifShadeSceneKey
- },
-
- // TODO(b/338577208): Remove once we add Dual Shade invocation zones.
- swipeDownFromTop(pointerCount = 2) to
- UserActionResult(
- toScene = SceneFamilies.QuickSettings,
- transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
- ),
-
- // Swiping down, not from the edge, always navigates to the notif shade scene.
- swipeDown(pointerCount = 1) to notifShadeSceneKey,
- swipeDown(pointerCount = 2) to notifShadeSceneKey,
- )
- .filterValuesNotNull()
- }
+ }
private fun swipeDownFromTop(pointerCount: Int): Swipe {
return Swipe(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index c7273b7..378374e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.LayoutDirection
import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -71,7 +72,10 @@
val keyguardTranslationX: Flow<StateToValue> =
configurationInteractor
- .dimensionPixelSize(R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x)
+ .directionalDimensionPixelSize(
+ LayoutDirection.LTR,
+ R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x
+ )
.flatMapLatest { translatePx: Int ->
transitionAnimation.sharedFlowWithState(
duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardQuickAffordancesLog.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardQuickAffordancesLog.kt
index 37c9552..e9cf7e2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardQuickAffordancesLog.kt
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.systemui.log.dagger
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
-import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
+import javax.inject.Qualifier
-val Kosmos.partitionedGridLayout by
- Kosmos.Fixture { PartitionedGridLayout(partitionedGridViewModel) }
+/** A [com.android.systemui.log.LogBuffer] for keyguard quick affordances related stuff. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class KeyguardQuickAffordancesLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index b2ba0e1..40bb8e1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -568,6 +568,16 @@
}
/**
+ * Provides a {@link LogBuffer} for keyguard quick affordances-related logs.
+ */
+ @Provides
+ @SysUISingleton
+ @KeyguardQuickAffordancesLog
+ public static LogBuffer provideKeyguardQuickAffordancesLogBuffer(LogBufferFactory factory) {
+ return factory.create("KeyguardQuickAffordancesLog", 25);
+ }
+
+ /**
* Provides a {@link LogBuffer} for keyguard transition animation logs.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 6c53374..cc4a92c 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -187,70 +187,40 @@
}
}
- CharSequence dialogText = null;
- CharSequence dialogTitle = null;
-
final String appName = extractAppName(aInfo, packageManager);
final boolean hasCastingCapabilities =
Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName);
- if (hasCastingCapabilities) {
- dialogText = getString(R.string.media_projection_sys_service_dialog_warning);
- dialogTitle = getString(R.string.media_projection_sys_service_dialog_title);
- } else {
- String actionText = getString(R.string.media_projection_dialog_warning, appName);
- SpannableString message = new SpannableString(actionText);
-
- int appNameIndex = actionText.indexOf(appName);
- if (appNameIndex >= 0) {
- message.setSpan(new StyleSpan(Typeface.BOLD),
- appNameIndex, appNameIndex + appName.length(), 0);
- }
- dialogText = message;
- dialogTitle = getString(R.string.media_projection_dialog_title, appName);
- }
-
// Using application context for the dialog, instead of the activity context, so we get
// the correct screen width when in split screen.
Context dialogContext = getApplicationContext();
- if (isPartialScreenSharingEnabled()) {
- final boolean overrideDisableSingleAppOption =
- CompatChanges.isChangeEnabled(
- OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
- mPackageName, getHostUserHandle());
- MediaProjectionPermissionDialogDelegate delegate =
- new MediaProjectionPermissionDialogDelegate(
- dialogContext,
- getMediaProjectionConfig(),
- dialog -> {
- ScreenShareOption selectedOption =
- dialog.getSelectedScreenShareOption();
- grantMediaProjectionPermission(selectedOption.getMode());
- },
- () -> finish(RECORD_CANCEL, /* projection= */ null),
- hasCastingCapabilities,
- appName,
- overrideDisableSingleAppOption,
- mUid,
- mMediaProjectionMetricsLogger);
- mDialog =
- new AlertDialogWithDelegate(
- dialogContext, R.style.Theme_SystemUI_Dialog, delegate);
- } else {
- AlertDialog.Builder dialogBuilder =
- new AlertDialog.Builder(dialogContext, R.style.Theme_SystemUI_Dialog)
- .setTitle(dialogTitle)
- .setIcon(R.drawable.ic_media_projection_permission)
- .setMessage(dialogText)
- .setPositiveButton(R.string.media_projection_action_text, this)
- .setNeutralButton(android.R.string.cancel, this);
- mDialog = dialogBuilder.create();
- }
+ final boolean overrideDisableSingleAppOption =
+ CompatChanges.isChangeEnabled(
+ OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+ mPackageName, getHostUserHandle());
+ MediaProjectionPermissionDialogDelegate delegate =
+ new MediaProjectionPermissionDialogDelegate(
+ dialogContext,
+ getMediaProjectionConfig(),
+ dialog -> {
+ ScreenShareOption selectedOption =
+ dialog.getSelectedScreenShareOption();
+ grantMediaProjectionPermission(selectedOption.getMode());
+ },
+ () -> finish(RECORD_CANCEL, /* projection= */ null),
+ hasCastingCapabilities,
+ appName,
+ overrideDisableSingleAppOption,
+ mUid,
+ mMediaProjectionMetricsLogger);
+ mDialog =
+ new AlertDialogWithDelegate(
+ dialogContext, R.style.Theme_SystemUI_Dialog, delegate);
if (savedInstanceState == null) {
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
mUid,
- appName == null
+ hasCastingCapabilities
? SessionCreationSource.CAST
: SessionCreationSource.APP);
}
@@ -366,7 +336,7 @@
setResult(RESULT_OK, intent);
finish(RECORD_CONTENT_DISPLAY, projection);
}
- if (isPartialScreenSharingEnabled() && screenShareMode == SINGLE_APP) {
+ if (screenShareMode == SINGLE_APP) {
IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection(
mUid, mPackageName, mReviewGrantedConsentRequired);
final Intent intent = new Intent(this,
@@ -437,8 +407,4 @@
return intent.getParcelableExtra(
MediaProjectionManager.EXTRA_MEDIA_PROJECTION_CONFIG);
}
-
- private boolean isPartialScreenSharingEnabled() {
- return mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
index 1f74716..fa8e13a 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
@@ -25,9 +25,8 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeAlignment
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
/** Models UI state and handles user input for the Notifications Shade scene. */
@SysUISingleton
@@ -36,16 +35,15 @@
constructor(
shadeInteractor: ShadeInteractor,
) {
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- MutableStateFlow(
- mapOf(
- if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
- Swipe.Up
- } else {
- Swipe.Down
- } to SceneFamilies.Home,
- Back to SceneFamilies.Home,
- )
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ flowOf(
+ mapOf(
+ if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+ Swipe.Up
+ } else {
+ Swipe.Down
+ } to SceneFamilies.Home,
+ Back to SceneFamilies.Home,
)
- .asStateFlow()
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index 78f4b4b..072d322 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -30,14 +30,10 @@
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
import com.android.systemui.qs.panels.shared.model.PanelsLog
-import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
-import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout
-import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
-import com.android.systemui.qs.panels.ui.compose.StretchedGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModelImpl
import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModel
@@ -102,22 +98,6 @@
@Provides
@IntoSet
- fun provideStretchedGridLayout(
- gridLayout: StretchedGridLayout
- ): Pair<GridLayoutType, GridLayout> {
- return Pair(StretchedGridLayoutType, gridLayout)
- }
-
- @Provides
- @IntoSet
- fun providePartitionedGridLayout(
- gridLayout: PartitionedGridLayout
- ): Pair<GridLayoutType, GridLayout> {
- return Pair(PartitionedGridLayoutType, gridLayout)
- }
-
- @Provides
- @IntoSet
fun providePaginatedGridLayout(
gridLayout: PaginatedGridLayout
): Pair<GridLayoutType, GridLayout> {
@@ -148,22 +128,6 @@
@Provides
@IntoSet
- fun provideStretchedGridConsistencyInteractor(
- consistencyInteractor: NoopGridConsistencyInteractor
- ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
- return Pair(StretchedGridLayoutType, consistencyInteractor)
- }
-
- @Provides
- @IntoSet
- fun providePartitionedGridConsistencyInteractor(
- consistencyInteractor: NoopGridConsistencyInteractor
- ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
- return Pair(PartitionedGridLayoutType, consistencyInteractor)
- }
-
- @Provides
- @IntoSet
fun providePaginatedGridConsistencyInteractor(
@PaginatedBaseLayoutType consistencyInteractor: GridTypeConsistencyInteractor,
): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
index b1942fe..323f39b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
@@ -26,14 +26,5 @@
/** Grid type representing a scrollable vertical grid. */
data object InfiniteGridLayoutType : GridLayoutType
-/**
- * Grid type representing a scrollable vertical grid where tiles will stretch to fill in empty
- * spaces.
- */
-data object StretchedGridLayoutType : GridLayoutType
-
-/** Grid type grouping large tiles on top and icon tiles at the bottom. */
-data object PartitionedGridLayoutType : GridLayoutType
-
/** Grid type for a paginated list of tiles. It will delegate to some other layout type. */
data object PaginatedGridLayoutType : GridLayoutType
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
deleted file mode 100644
index 6c84edd..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.ui.compose
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.GridItemSpan
-import androidx.compose.foundation.lazy.grid.LazyGridScope
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Switch
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithContent
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.PathEffect
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.graphics.addOutline
-import androidx.compose.ui.graphics.drawscope.Stroke
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.compose.modifiers.background
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.PartitionedGridViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
-import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.res.R
-import javax.inject.Inject
-
-@SysUISingleton
-class PartitionedGridLayout @Inject constructor(private val viewModel: PartitionedGridViewModel) :
- PaginatableGridLayout {
- @Composable
- override fun TileGrid(
- tiles: List<TileViewModel>,
- modifier: Modifier,
- editModeStart: () -> Unit,
- ) {
- DisposableEffect(tiles) {
- val token = Any()
- tiles.forEach { it.startListening(token) }
- onDispose { tiles.forEach { it.stopListening(token) } }
- }
- val columns by viewModel.columns.collectAsStateWithLifecycle()
- val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()
- val largeTileHeight = tileHeight()
- val iconTileHeight = tileHeight(showLabels)
- val (smallTiles, largeTiles) = tiles.partition { viewModel.isIconTile(it.spec) }
-
- TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
- // Large tiles
- items(largeTiles.size, span = { GridItemSpan(2) }) { index ->
- Tile(
- tile = largeTiles[index],
- iconOnly = false,
- modifier = Modifier.height(largeTileHeight)
- )
- }
- fillUpRow(nTiles = largeTiles.size, columns = columns / 2)
-
- // Small tiles
- items(smallTiles.size) { index ->
- Tile(
- tile = smallTiles[index],
- iconOnly = true,
- showLabels = showLabels,
- modifier = Modifier.height(iconTileHeight)
- )
- }
- }
- }
-
- @Composable
- override fun EditTileGrid(
- tiles: List<EditTileViewModel>,
- modifier: Modifier,
- onAddTile: (TileSpec, Int) -> Unit,
- onRemoveTile: (TileSpec) -> Unit,
- ) {
- val columns by viewModel.columns.collectAsStateWithLifecycle()
- val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()
-
- val listState = rememberEditListState(tiles)
- val dragAndDropState = rememberDragAndDropState(listState)
-
- val (currentTiles, otherTiles) = listState.tiles.partition { it.isCurrent }
- val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
- onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
- }
- val onDoubleTap: (TileSpec) -> Unit by rememberUpdatedState { tileSpec ->
- viewModel.resize(tileSpec, !viewModel.isIconTile(tileSpec))
- }
- val largeTileHeight = tileHeight()
- val iconTileHeight = tileHeight(showLabels)
- val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
-
- Column(
- verticalArrangement = Arrangement.spacedBy(tilePadding),
- modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState())
- ) {
- Row(
- modifier =
- Modifier.background(
- color = MaterialTheme.colorScheme.surfaceVariant,
- alpha = { 1f },
- shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))
- )
- .padding(tilePadding)
- ) {
- Column(Modifier.padding(start = tilePadding)) {
- Text(
- text = "Show text labels",
- color = MaterialTheme.colorScheme.onBackground,
- fontWeight = FontWeight.Bold
- )
- Text(
- text = "Display names under each tile",
- color = MaterialTheme.colorScheme.onBackground
- )
- }
- Spacer(modifier = Modifier.weight(1f))
- Switch(checked = showLabels, onCheckedChange = { viewModel.setShowLabels(it) })
- }
-
- CurrentTiles(
- tiles = currentTiles,
- largeTileHeight = largeTileHeight,
- iconTileHeight = iconTileHeight,
- tilePadding = tilePadding,
- onAdd = onAddTile,
- onRemove = onRemoveTile,
- onDoubleTap = onDoubleTap,
- isIconOnly = viewModel::isIconTile,
- columns = columns,
- showLabels = showLabels,
- dragAndDropState = dragAndDropState,
- )
- AvailableTiles(
- tiles = otherTiles.filter { !dragAndDropState.isMoving(it.tileSpec) },
- largeTileHeight = largeTileHeight,
- iconTileHeight = iconTileHeight,
- tilePadding = tilePadding,
- addTileToEnd = addTileToEnd,
- onRemove = onRemoveTile,
- onDoubleTap = onDoubleTap,
- isIconOnly = viewModel::isIconTile,
- showLabels = showLabels,
- columns = columns,
- dragAndDropState = dragAndDropState,
- )
- }
- }
-
- override fun splitIntoPages(
- tiles: List<TileViewModel>,
- rows: Int,
- columns: Int,
- ): List<List<TileViewModel>> {
- val (smallTiles, largeTiles) = tiles.partition { viewModel.isIconTile(it.spec) }
-
- val sizedLargeTiles = largeTiles.map { SizedTile(it, 2) }
- val sizedSmallTiles = smallTiles.map { SizedTile(it, 1) }
- val largeTilesRows = PaginatableGridLayout.splitInRows(sizedLargeTiles, columns)
- val smallTilesRows = PaginatableGridLayout.splitInRows(sizedSmallTiles, columns)
- return (largeTilesRows + smallTilesRows).chunked(rows).map { it.flatten().map { it.tile } }
- }
-
- @Composable
- private fun CurrentTiles(
- tiles: List<EditTileViewModel>,
- largeTileHeight: Dp,
- iconTileHeight: Dp,
- tilePadding: Dp,
- onAdd: (TileSpec, Int) -> Unit,
- onRemove: (TileSpec) -> Unit,
- onDoubleTap: (TileSpec) -> Unit,
- isIconOnly: (TileSpec) -> Boolean,
- showLabels: Boolean,
- columns: Int,
- dragAndDropState: DragAndDropState,
- ) {
- val (smallTiles, largeTiles) = tiles.partition { isIconOnly(it.tileSpec) }
-
- val largeGridHeight = gridHeight(largeTiles.size, largeTileHeight, columns / 2, tilePadding)
- val smallGridHeight = gridHeight(smallTiles.size, iconTileHeight, columns, tilePadding)
-
- CurrentTilesContainer {
- TileLazyGrid(
- columns = GridCells.Fixed(columns),
- modifier =
- Modifier.height(largeGridHeight)
- .dragAndDropTileList(dragAndDropState, { !isIconOnly(it) }, onAdd)
- ) {
- editTiles(
- tiles = largeTiles,
- clickAction = ClickAction.REMOVE,
- onClick = onRemove,
- onDoubleTap = onDoubleTap,
- isIconOnly = { false },
- dragAndDropState = dragAndDropState,
- acceptDrops = { !isIconOnly(it) },
- onDrop = onAdd,
- indicatePosition = true,
- )
- }
- }
-
- CurrentTilesContainer {
- TileLazyGrid(
- columns = GridCells.Fixed(columns),
- modifier =
- Modifier.height(smallGridHeight)
- .dragAndDropTileList(dragAndDropState, { isIconOnly(it) }, onAdd)
- ) {
- editTiles(
- tiles = smallTiles,
- clickAction = ClickAction.REMOVE,
- onClick = onRemove,
- onDoubleTap = onDoubleTap,
- isIconOnly = { true },
- showLabels = showLabels,
- dragAndDropState = dragAndDropState,
- acceptDrops = { isIconOnly(it) },
- onDrop = onAdd,
- indicatePosition = true,
- )
- }
- }
- }
-
- @Composable
- private fun AvailableTiles(
- tiles: List<EditTileViewModel>,
- largeTileHeight: Dp,
- iconTileHeight: Dp,
- tilePadding: Dp,
- addTileToEnd: (TileSpec) -> Unit,
- onRemove: (TileSpec) -> Unit,
- onDoubleTap: (TileSpec) -> Unit,
- isIconOnly: (TileSpec) -> Boolean,
- showLabels: Boolean,
- columns: Int,
- dragAndDropState: DragAndDropState,
- ) {
- val (tilesStock, tilesCustom) = tiles.partition { it.appName == null }
- val (smallTiles, largeTiles) = tilesStock.partition { isIconOnly(it.tileSpec) }
-
- val largeGridHeight = gridHeight(largeTiles.size, largeTileHeight, columns / 2, tilePadding)
- val smallGridHeight = gridHeight(smallTiles.size, iconTileHeight, columns, tilePadding)
- val largeGridHeightCustom =
- gridHeight(tilesCustom.size, iconTileHeight, columns, tilePadding)
-
- // Add up the height of all three grids and add padding in between
- val gridHeight =
- largeGridHeight + smallGridHeight + largeGridHeightCustom + (tilePadding * 2)
-
- val onDrop: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, _ ->
- onRemove(tileSpec)
- }
-
- AvailableTilesContainer {
- TileLazyGrid(
- columns = GridCells.Fixed(columns),
- modifier =
- Modifier.height(gridHeight)
- .dragAndDropTileList(dragAndDropState, { true }, onDrop)
- ) {
- // Large tiles
- editTiles(
- largeTiles,
- ClickAction.ADD,
- addTileToEnd,
- isIconOnly,
- dragAndDropState,
- onDoubleTap = onDoubleTap,
- acceptDrops = { true },
- onDrop = onDrop,
- )
- fillUpRow(nTiles = largeTiles.size, columns = columns / 2)
-
- // Small tiles
- editTiles(
- smallTiles,
- ClickAction.ADD,
- addTileToEnd,
- isIconOnly,
- dragAndDropState,
- onDoubleTap = onDoubleTap,
- showLabels = showLabels,
- acceptDrops = { true },
- onDrop = onDrop,
- )
- fillUpRow(nTiles = smallTiles.size, columns = columns)
-
- // Custom tiles, all icons
- editTiles(
- tilesCustom,
- ClickAction.ADD,
- addTileToEnd,
- isIconOnly,
- dragAndDropState,
- onDoubleTap = onDoubleTap,
- showLabels = showLabels,
- acceptDrops = { true },
- onDrop = onDrop,
- )
- }
- }
- }
-
- @Composable
- private fun CurrentTilesContainer(content: @Composable () -> Unit) {
- Box(
- Modifier.fillMaxWidth()
- .dashedBorder(
- color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f),
- shape = Dimensions.ContainerShape,
- )
- .padding(dimensionResource(R.dimen.qs_tile_margin_vertical))
- ) {
- content()
- }
- }
-
- @Composable
- private fun AvailableTilesContainer(content: @Composable () -> Unit) {
- Box(
- Modifier.fillMaxWidth()
- .background(
- color = MaterialTheme.colorScheme.background,
- alpha = { 1f },
- shape = Dimensions.ContainerShape,
- )
- .padding(dimensionResource(R.dimen.qs_tile_margin_vertical))
- ) {
- content()
- }
- }
-
- /** Fill up the rest of the row if it's not complete. */
- private fun LazyGridScope.fillUpRow(nTiles: Int, columns: Int) {
- if (nTiles % columns != 0) {
- item(span = { GridItemSpan(maxCurrentLineSpan) }) { Spacer(Modifier) }
- }
- }
-
- private fun Modifier.dashedBorder(
- color: Color,
- shape: Shape,
- ): Modifier {
- return this.drawWithContent {
- val outline = shape.createOutline(size, layoutDirection, this)
- val path = Path()
- path.addOutline(outline)
- val stroke =
- Stroke(
- width = 1.dp.toPx(),
- pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f))
- )
- this.drawContent()
- drawPath(path = path, style = stroke, color = color)
- }
- }
-
- private object Dimensions {
- // Corner radius is half the height of a tile + padding
- val ContainerShape = RoundedCornerShape(48.dp)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
deleted file mode 100644
index 3e48245..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.ui.compose
-
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.GridItemSpan
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.dimensionResource
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.shared.model.TileRow
-import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.res.R
-import javax.inject.Inject
-
-@SysUISingleton
-class StretchedGridLayout
-@Inject
-constructor(
- private val iconTilesViewModel: IconTilesViewModel,
- private val gridSizeViewModel: FixedColumnsSizeViewModel,
-) : GridLayout {
-
- @Composable
- override fun TileGrid(
- tiles: List<TileViewModel>,
- modifier: Modifier,
- editModeStart: () -> Unit,
- ) {
- DisposableEffect(tiles) {
- val token = Any()
- tiles.forEach { it.startListening(token) }
- onDispose { tiles.forEach { it.stopListening(token) } }
- }
-
- // Tile widths [normal|stretched]
- // Icon [3 | 4]
- // Large [6 | 8]
- val columns = 12
- val stretchedTiles =
- remember(tiles) {
- val sizedTiles =
- tiles.map {
- SizedTile(
- it,
- if (iconTilesViewModel.isIconTile(it.spec)) {
- 3
- } else {
- 6
- }
- )
- }
- splitInRows(sizedTiles, columns)
- }
-
- TileLazyGrid(columns = GridCells.Fixed(columns), modifier = modifier) {
- items(stretchedTiles.size, span = { GridItemSpan(stretchedTiles[it].width) }) { index ->
- Tile(
- tile = stretchedTiles[index].tile,
- iconOnly = iconTilesViewModel.isIconTile(stretchedTiles[index].tile.spec),
- modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
- )
- }
- }
- }
-
- @Composable
- override fun EditTileGrid(
- tiles: List<EditTileViewModel>,
- modifier: Modifier,
- onAddTile: (TileSpec, Int) -> Unit,
- onRemoveTile: (TileSpec) -> Unit
- ) {
- val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
-
- DefaultEditTileGrid(
- tiles = tiles,
- isIconOnly = iconTilesViewModel::isIconTile,
- columns = columns,
- modifier = modifier,
- onAddTile = onAddTile,
- onRemoveTile = onRemoveTile,
- onResize = iconTilesViewModel::resize,
- )
- }
-
- private fun splitInRows(
- tiles: List<SizedTile<TileViewModel>>,
- columns: Int
- ): List<SizedTile<TileViewModel>> {
- val row = TileRow<TileViewModel>(columns)
-
- return buildList {
- for (tile in tiles) {
- if (row.maybeAddTile(tile)) {
- if (row.isFull()) {
- // Row is full, no need to stretch tiles
- addAll(row.tiles)
- row.clear()
- }
- } else {
- if (row.isFull()) {
- addAll(row.tiles)
- } else {
- // Stretching tiles when row isn't full
- addAll(row.tiles.map { it.copy(width = it.width + (it.width / 3)) })
- }
- row.clear()
- row.maybeAddTile(tile)
- }
- }
- addAll(row.tiles)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index edc49cac2..f018336 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -489,6 +489,10 @@
mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
mMobileToggleDivider.setVisibility(
mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
+ int primaryColor = isNetworkConnected
+ ? R.color.connected_network_primary_color
+ : R.color.disconnected_network_primary_color;
+ mMobileToggleDivider.setBackgroundColor(dialog.getContext().getColor(primaryColor));
// Display the info for the non-DDS if it's actively being used
int autoSwitchNonDdsSubId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
index 4c6563d..083bf05 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
@@ -16,14 +16,10 @@
package com.android.systemui.qs.tiles.impl.modes.domain.interactor
-//noinspection CleanArchitectureDependencyViolation: dialog needs to be opened on click
import android.content.Intent
import android.provider.Settings
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.animation.DialogCuj
-import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInput
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
@@ -31,15 +27,13 @@
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.withContext
+@SysUISingleton
class ModesTileUserActionInteractor
@Inject
constructor(
- @Main private val coroutineContext: CoroutineContext,
- private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
- private val dialogTransitionAnimator: DialogTransitionAnimator,
+ private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler,
+ // TODO(b/353896370): The domain layer should not have to depend on the UI layer.
private val dialogDelegate: ModesDialogDelegate,
) : QSTileUserActionInteractor<ModesTileModel> {
val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
@@ -51,29 +45,14 @@
handleClick(action.expandable)
}
is QSTileUserAction.LongClick -> {
- qsTileIntentUserActionHandler.handle(action.expandable, longClickIntent)
+ qsTileIntentUserInputHandler.handle(action.expandable, longClickIntent)
}
}
}
}
suspend fun handleClick(expandable: Expandable?) {
- // Show a dialog with the list of modes to configure. Dialogs shown by the
- // DialogTransitionAnimator must be created and shown on the main thread, so we post it to
- // the UI handler.
- withContext(coroutineContext) {
- val dialog = dialogDelegate.createDialog()
-
- expandable
- ?.dialogTransitionController(
- DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
- )
- ?.let { controller -> dialogTransitionAnimator.show(dialog, controller) }
- ?: dialog.show()
- }
- }
-
- companion object {
- private const val INTERACTION_JANK_TAG = "configure_priority_modes"
+ // Show a dialog with the list of modes to configure.
+ dialogDelegate.showDialog(expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 79cdfec..b1cc55d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -25,7 +25,6 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -38,20 +37,17 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the quick settings scene. */
@SysUISingleton
class QuickSettingsSceneViewModel
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
val brightnessMirrorViewModel: BrightnessMirrorViewModel,
val shadeHeaderViewModel: ShadeHeaderViewModel,
val qsSceneAdapter: QSSceneAdapter,
@@ -61,55 +57,35 @@
sceneBackInteractor: SceneBackInteractor,
val mediaCarouselInteractor: MediaCarouselInteractor,
) {
- private val backScene: StateFlow<SceneKey> =
+ private val backScene: Flow<SceneKey> =
sceneBackInteractor.backScene
.filter { it != Scenes.QuickSettings }
.map { it ?: Scenes.Shade }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = Scenes.Shade,
- )
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
combine(
- qsSceneAdapter.isCustomizerShowing,
- backScene,
- transform = ::destinationScenes,
- )
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- destinationScenes(
- isCustomizing = qsSceneAdapter.isCustomizerShowing.value,
- backScene = backScene.value,
- ),
- )
+ qsSceneAdapter.isCustomizerShowing,
+ backScene,
+ ) { isCustomizing, backScene ->
+ buildMap<UserAction, UserActionResult> {
+ if (isCustomizing) {
+ // TODO(b/332749288) Empty map so there are no back handlers and back can close
+ // customizer
- val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
-
- private fun destinationScenes(
- isCustomizing: Boolean,
- backScene: SceneKey?,
- ): Map<UserAction, UserActionResult> {
- return buildMap {
- if (isCustomizing) {
- // TODO(b/332749288) Empty map so there are no back handlers and back can close
- // customizer
-
- // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
- // while customizing
- } else {
- put(Back, UserActionResult(backScene ?: Scenes.Shade))
- put(Swipe(SwipeDirection.Up), UserActionResult(backScene ?: Scenes.Shade))
- put(
- Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up),
- UserActionResult(SceneFamilies.Home),
- )
+ // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
+ // while customizing
+ } else {
+ put(Back, UserActionResult(backScene))
+ put(Swipe(SwipeDirection.Up), UserActionResult(backScene))
+ put(
+ Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up),
+ UserActionResult(SceneFamilies.Home),
+ )
+ }
}
}
- }
+
+ val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
private val footerActionsControllerInitialized = AtomicBoolean(false)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
index 66fcbac..e012f2c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
@@ -21,17 +21,13 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeAlignment
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the Quick Settings Shade scene. */
@SysUISingleton
@@ -41,33 +37,22 @@
private val shadeInteractor: ShadeInteractor,
val overlayShadeViewModel: OverlayShadeViewModel,
val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
- @Application applicationScope: CoroutineScope,
) {
- val isEditing: StateFlow<Boolean> = quickSettingsContainerViewModel.editModeViewModel.isEditing
-
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- isEditing
- .map { editing -> destinations(editing) }
- .stateIn(
- applicationScope,
- SharingStarted.WhileSubscribed(),
- destinations(isEditing.value)
- )
-
- private fun destinations(editing: Boolean): Map<UserAction, UserActionResult> {
- return buildMap {
- put(
- if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
- Swipe.Up
- } else {
- Swipe.Down
- },
- UserActionResult(SceneFamilies.Home)
- )
- if (!editing) {
- put(Back, UserActionResult(SceneFamilies.Home))
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ quickSettingsContainerViewModel.editModeViewModel.isEditing.map { editing ->
+ buildMap {
+ put(
+ if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+ Swipe.Up
+ } else {
+ Swipe.Down
+ },
+ UserActionResult(SceneFamilies.Home)
+ )
+ if (!editing) {
+ put(Back, UserActionResult(SceneFamilies.Home))
+ }
}
}
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index dd2dbf3..f8b3ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -35,7 +35,6 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.flags.Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.SessionCreationSource
@@ -154,10 +153,7 @@
SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
)
- if (
- flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) &&
- !state.hasUserApprovedScreenRecording
- ) {
+ if (!state.hasUserApprovedScreenRecording) {
mainExecutor.execute {
ScreenCapturePermissionDialogDelegate(factory, state).createDialog().apply {
setOnCancelListener { screenRecordSwitch.isChecked = false }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index 939d5bc..c7190c3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -19,7 +19,8 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
-import kotlinx.coroutines.flow.StateFlow
+import com.android.systemui.activatable.Activatable
+import kotlinx.coroutines.flow.Flow
/**
* Defines interface for classes that can describe a "scene".
@@ -29,11 +30,13 @@
* based on either user action (for example, swiping down while on the lock screen scene may switch
* to the shade scene).
*/
-interface Scene {
+interface Scene : Activatable {
/** Uniquely-identifying key for this scene. The key must be unique within its container. */
val key: SceneKey
+ override suspend fun activate() = Unit
+
/**
* The mapping between [UserAction] and destination [UserActionResult]s.
*
@@ -54,5 +57,5 @@
* type is not currently active in the scene and should be ignored by the framework, while the
* current scene is this one.
*/
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>>
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>>
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index c20d577..d31d6f4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -103,6 +103,7 @@
windowInsets = windowInsets,
sceneByKey = sortedSceneByKey,
dataSourceDelegator = dataSourceDelegator,
+ containerConfig = containerConfig,
)
.also { it.id = R.id.scene_container_root_composable }
)
@@ -141,6 +142,7 @@
windowInsets: StateFlow<WindowInsets?>,
sceneByKey: Map<SceneKey, Scene>,
dataSourceDelegator: SceneDataSourceDelegator,
+ containerConfig: SceneContainerConfig,
): View {
return ComposeView(context).apply {
setContent {
@@ -153,6 +155,7 @@
viewModel = viewModel,
sceneByKey =
sceneByKey.mapValues { (_, scene) -> scene as ComposableScene },
+ initialSceneKey = containerConfig.initialSceneKey,
dataSourceDelegator = dataSourceDelegator,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
index 9f48ee9..b739ffe 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
@@ -23,79 +23,61 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.TransitionKeys.OpenBottomShade
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
@SysUISingleton
class GoneSceneViewModel
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
private val shadeInteractor: ShadeInteractor,
) {
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- shadeInteractor.shadeMode
- .map(::destinationScenes)
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- destinationScenes(
- shadeMode = shadeInteractor.shadeMode.value,
- )
- )
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ shadeInteractor.shadeMode.map { shadeMode ->
+ buildMap {
+ if (
+ shadeMode is ShadeMode.Single ||
+ // TODO(b/338577208): Remove this once we add Dual Shade invocation zones.
+ shadeMode is ShadeMode.Dual
+ ) {
+ if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
+ put(
+ Swipe(
+ pointerCount = 2,
+ fromSource = Edge.Bottom,
+ direction = SwipeDirection.Up,
+ ),
+ UserActionResult(SceneFamilies.QuickSettings, OpenBottomShade)
+ )
+ } else {
+ put(
+ Swipe(
+ pointerCount = 2,
+ fromSource = Edge.Top,
+ direction = SwipeDirection.Down,
+ ),
+ UserActionResult(SceneFamilies.QuickSettings)
+ )
+ }
+ }
- private fun destinationScenes(
- shadeMode: ShadeMode,
- ): Map<UserAction, UserActionResult> {
- return buildMap {
- if (
- shadeMode is ShadeMode.Single ||
- // TODO(b/338577208): Remove this once we add Dual Shade invocation zones.
- shadeMode is ShadeMode.Dual
- ) {
if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
- put(
- Swipe(
- pointerCount = 2,
- fromSource = Edge.Bottom,
- direction = SwipeDirection.Up,
- ),
- UserActionResult(SceneFamilies.QuickSettings, OpenBottomShade)
- )
+ put(Swipe.Up, UserActionResult(SceneFamilies.NotifShade, OpenBottomShade))
} else {
put(
- Swipe(
- pointerCount = 2,
- fromSource = Edge.Top,
- direction = SwipeDirection.Down,
- ),
- UserActionResult(SceneFamilies.QuickSettings)
+ Swipe.Down,
+ UserActionResult(
+ SceneFamilies.NotifShade,
+ ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+ )
)
}
}
-
- if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
- put(Swipe.Up, UserActionResult(SceneFamilies.NotifShade, OpenBottomShade))
- } else {
- put(
- Swipe.Down,
- UserActionResult(
- SceneFamilies.NotifShade,
- ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
- )
- )
- }
}
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 46c5861..a8a78a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -171,11 +171,8 @@
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
getHostUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
- return (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
- ? mScreenRecordPermissionDialogDelegateFactory
- .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked)
- : mScreenRecordDialogFactory
- .create(this, onStartRecordingClicked))
+ return mScreenRecordPermissionDialogDelegateFactory
+ .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked)
.createDialog();
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SystemUI/src/com/android/systemui/screenshot/InteractiveScreenshotHandler.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/screenshot/InteractiveScreenshotHandler.kt
index 37c9552..26405f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/InteractiveScreenshotHandler.kt
@@ -14,11 +14,20 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.systemui.screenshot
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
-import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
+import android.view.Display
-val Kosmos.partitionedGridLayout by
- Kosmos.Fixture { PartitionedGridLayout(partitionedGridViewModel) }
+interface InteractiveScreenshotHandler : ScreenshotHandler {
+ fun isPendingSharedTransition(): Boolean
+
+ fun requestDismissal(event: ScreenshotEvent)
+
+ fun removeWindow()
+
+ fun onDestroy()
+
+ interface Factory {
+ fun create(display: Display): InteractiveScreenshotHandler
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
new file mode 100644
index 0000000..a2583e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
@@ -0,0 +1,848 @@
+/*
+ * 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.screenshot;
+
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+
+import static com.android.systemui.Flags.screenshotPrivateProfileAccessibilityAnnouncementFix;
+import static com.android.systemui.Flags.screenshotSaveImageExporter;
+import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
+import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
+import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
+import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
+import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
+import static com.android.systemui.screenshot.LogConfig.logTag;
+import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER;
+import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ICompatCameraControlCallback;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.ScrollCaptureResponse;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.Toast;
+import android.window.WindowContext;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.policy.PhoneWindow;
+import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
+import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
+import com.android.systemui.screenshot.scroll.ScrollCaptureExecutor;
+import com.android.systemui.util.Assert;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
+import kotlin.Unit;
+
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+import javax.inject.Provider;
+
+/**
+ * Controls the state and flow for screenshots.
+ */
+public class LegacyScreenshotController implements InteractiveScreenshotHandler {
+ private static final String TAG = logTag(LegacyScreenshotController.class);
+
+ // From WizardManagerHelper.java
+ private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
+
+ static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
+
+ private final WindowContext mContext;
+ private final FeatureFlags mFlags;
+ private final ScreenshotShelfViewProxy mViewProxy;
+ private final ScreenshotNotificationsController mNotificationsController;
+ private final ScreenshotSmartActions mScreenshotSmartActions;
+ private final UiEventLogger mUiEventLogger;
+ private final ImageExporter mImageExporter;
+ private final ImageCapture mImageCapture;
+ private final Executor mMainExecutor;
+ private final ExecutorService mBgExecutor;
+ private final BroadcastSender mBroadcastSender;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final ScreenshotActionsController mActionsController;
+
+ private final WindowManager mWindowManager;
+ private final WindowManager.LayoutParams mWindowLayoutParams;
+ @Nullable
+ private final ScreenshotSoundController mScreenshotSoundController;
+ private final PhoneWindow mWindow;
+ private final Display mDisplay;
+ private final ScrollCaptureExecutor mScrollCaptureExecutor;
+ private final ScreenshotNotificationSmartActionsProvider
+ mScreenshotNotificationSmartActionsProvider;
+ private final TimeoutHandler mScreenshotHandler;
+ private final UserManager mUserManager;
+ private final AssistContentRequester mAssistContentRequester;
+ private final ActionExecutor mActionExecutor;
+
+
+ private final MessageContainerController mMessageContainerController;
+ private final AnnouncementResolver mAnnouncementResolver;
+ private Bitmap mScreenBitmap;
+ private SaveImageInBackgroundTask mSaveInBgTask;
+ private boolean mScreenshotTakenInPortrait;
+ private boolean mAttachRequested;
+ private boolean mDetachRequested;
+ private Animator mScreenshotAnimation;
+ private RequestCallback mCurrentRequestCallback;
+ private String mPackageName = "";
+ private final BroadcastReceiver mCopyBroadcastReceiver;
+
+ /** Tracks config changes that require re-creating UI */
+ private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
+ ActivityInfo.CONFIG_ORIENTATION
+ | ActivityInfo.CONFIG_LAYOUT_DIRECTION
+ | ActivityInfo.CONFIG_LOCALE
+ | ActivityInfo.CONFIG_UI_MODE
+ | ActivityInfo.CONFIG_SCREEN_LAYOUT
+ | ActivityInfo.CONFIG_ASSETS_PATHS);
+
+
+ @AssistedInject
+ LegacyScreenshotController(
+ Context context,
+ WindowManager windowManager,
+ FeatureFlags flags,
+ ScreenshotShelfViewProxy.Factory viewProxyFactory,
+ ScreenshotSmartActions screenshotSmartActions,
+ ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
+ UiEventLogger uiEventLogger,
+ ImageExporter imageExporter,
+ ImageCapture imageCapture,
+ @Main Executor mainExecutor,
+ ScrollCaptureExecutor scrollCaptureExecutor,
+ TimeoutHandler timeoutHandler,
+ BroadcastSender broadcastSender,
+ BroadcastDispatcher broadcastDispatcher,
+ ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
+ ScreenshotActionsController.Factory screenshotActionsControllerFactory,
+ ActionExecutor.Factory actionExecutorFactory,
+ UserManager userManager,
+ AssistContentRequester assistContentRequester,
+ MessageContainerController messageContainerController,
+ Provider<ScreenshotSoundController> screenshotSoundController,
+ AnnouncementResolver announcementResolver,
+ @Assisted Display display
+ ) {
+ mScreenshotSmartActions = screenshotSmartActions;
+ mNotificationsController = screenshotNotificationsControllerFactory.create(
+ display.getDisplayId());
+ mUiEventLogger = uiEventLogger;
+ mImageExporter = imageExporter;
+ mImageCapture = imageCapture;
+ mMainExecutor = mainExecutor;
+ mScrollCaptureExecutor = scrollCaptureExecutor;
+ mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider;
+ mBgExecutor = Executors.newSingleThreadExecutor();
+ mBroadcastSender = broadcastSender;
+ mBroadcastDispatcher = broadcastDispatcher;
+
+ mScreenshotHandler = timeoutHandler;
+ mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
+
+ mDisplay = display;
+ mWindowManager = windowManager;
+ final Context displayContext = context.createDisplayContext(display);
+ mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
+ mFlags = flags;
+ mUserManager = userManager;
+ mMessageContainerController = messageContainerController;
+ mAssistContentRequester = assistContentRequester;
+ mAnnouncementResolver = announcementResolver;
+
+ mViewProxy = viewProxyFactory.getProxy(mContext, mDisplay.getDisplayId());
+
+ mScreenshotHandler.setOnTimeoutRunnable(() -> {
+ if (DEBUG_UI) {
+ Log.d(TAG, "Corner timeout hit");
+ }
+ mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT);
+ });
+
+ // Setup the window that we are going to use
+ mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
+ mWindowLayoutParams.setTitle("ScreenshotAnimation");
+
+ mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
+ mWindow.setWindowManager(mWindowManager, null, null);
+
+ mConfigChanges.applyNewConfig(context.getResources());
+ reloadAssets();
+
+ mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy,
+ () -> {
+ finishDismiss();
+ return Unit.INSTANCE;
+ });
+ mActionsController = screenshotActionsControllerFactory.getController(mActionExecutor);
+
+
+ // Sound is only reproduced from the controller of the default display.
+ if (mDisplay.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mScreenshotSoundController = screenshotSoundController.get();
+ } else {
+ mScreenshotSoundController = null;
+ }
+
+ mCopyBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) {
+ mViewProxy.requestDismissal(SCREENSHOT_DISMISSED_OTHER);
+ }
+ }
+ };
+ mBroadcastDispatcher.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
+ ClipboardOverlayController.COPY_OVERLAY_ACTION), null, null,
+ Context.RECEIVER_NOT_EXPORTED, ClipboardOverlayController.SELF_PERMISSION);
+ }
+
+ @Override
+ public void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher,
+ RequestCallback requestCallback) {
+ Assert.isMainThread();
+
+ mCurrentRequestCallback = requestCallback;
+ if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+ && screenshot.getBitmap() == null) {
+ Rect bounds = getFullScreenRect();
+ screenshot.setBitmap(mImageCapture.captureDisplay(mDisplay.getDisplayId(), bounds));
+ screenshot.setScreenBounds(bounds);
+ }
+
+ if (screenshot.getBitmap() == null) {
+ Log.e(TAG, "handleScreenshot: Screenshot bitmap was null");
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ if (mCurrentRequestCallback != null) {
+ mCurrentRequestCallback.reportError();
+ }
+ return;
+ }
+
+ mScreenBitmap = screenshot.getBitmap();
+ String oldPackageName = mPackageName;
+ mPackageName = screenshot.getPackageNameString();
+
+ if (!isUserSetupComplete(Process.myUserHandle())) {
+ Log.w(TAG, "User setup not complete, displaying toast only");
+ // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
+ // and sharing shouldn't be exposed to the user.
+ saveScreenshotAndToast(screenshot, finisher);
+ return;
+ }
+
+ mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
+ ClipboardOverlayController.SELF_PERMISSION);
+
+ mScreenshotTakenInPortrait =
+ mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
+
+ // Optimizations
+ mScreenBitmap.setHasAlpha(false);
+ mScreenBitmap.prepareToDraw();
+
+ prepareViewForNewScreenshot(screenshot, oldPackageName);
+
+ final UUID requestId;
+ requestId = mActionsController.setCurrentScreenshot(screenshot);
+ saveScreenshotInBackground(screenshot, requestId, finisher, result -> {
+ if (result.uri != null) {
+ ScreenshotSavedResult savedScreenshot = new ScreenshotSavedResult(
+ result.uri, screenshot.getUserOrDefault(), result.timestamp);
+ mActionsController.setCompletedScreenshot(requestId, savedScreenshot);
+ }
+ });
+
+ if (screenshot.getTaskId() >= 0) {
+ mAssistContentRequester.requestAssistContent(
+ screenshot.getTaskId(),
+ assistContent ->
+ mActionsController.onAssistContent(requestId, assistContent));
+ } else {
+ mActionsController.onAssistContent(requestId, null);
+ }
+
+ // The window is focusable by default
+ setWindowFocusable(true);
+ mViewProxy.requestFocus();
+
+ enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle());
+
+ attachWindow();
+
+ boolean showFlash;
+ if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ if (screenshot.getScreenBounds() != null
+ && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(),
+ screenshot.getScreenBounds())) {
+ showFlash = false;
+ } else {
+ showFlash = true;
+ screenshot.setInsets(Insets.NONE);
+ screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(),
+ screenshot.getBitmap().getHeight()));
+ }
+ } else {
+ showFlash = true;
+ }
+
+ mViewProxy.prepareEntranceAnimation(
+ () -> startAnimation(screenshot.getScreenBounds(), showFlash,
+ () -> mMessageContainerController.onScreenshotTaken(screenshot)));
+
+ mViewProxy.setScreenshot(screenshot);
+
+ // ignore system bar insets for the purpose of window layout
+ mWindow.getDecorView().setOnApplyWindowInsetsListener(
+ (v, insets) -> WindowInsets.CONSUMED);
+ }
+
+ void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) {
+ withWindowAttached(() -> {
+ if (screenshotPrivateProfileAccessibilityAnnouncementFix()) {
+ mAnnouncementResolver.getScreenshotAnnouncement(
+ screenshot.getUserHandle().getIdentifier(),
+ mViewProxy::announceForAccessibility);
+ } else {
+ if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
+ mViewProxy.announceForAccessibility(mContext.getResources().getString(
+ R.string.screenshot_saving_work_profile_title));
+ } else {
+ mViewProxy.announceForAccessibility(
+ mContext.getResources().getString(R.string.screenshot_saving_title));
+ }
+ }
+ });
+
+ mViewProxy.reset();
+
+ if (mViewProxy.isAttachedToWindow()) {
+ // if we didn't already dismiss for another reason
+ if (!mViewProxy.isDismissing()) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0,
+ oldPackageName);
+ }
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
+ + "(dismissing=" + mViewProxy.isDismissing() + ")");
+ }
+ }
+
+ mViewProxy.setPackageName(mPackageName);
+ }
+
+ /**
+ * Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already
+ * being dismissed)
+ */
+ @Override
+ public void requestDismissal(ScreenshotEvent event) {
+ mViewProxy.requestDismissal(event);
+ }
+
+ @Override
+ public boolean isPendingSharedTransition() {
+ return mActionExecutor.isPendingSharedTransition();
+ }
+
+ // Any cleanup needed when the service is being destroyed.
+ @Override
+ public void onDestroy() {
+ if (mSaveInBgTask != null) {
+ // just log success/failure for the pre-existing screenshot
+ mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
+ }
+ removeWindow();
+ releaseMediaPlayer();
+ releaseContext();
+ mBgExecutor.shutdown();
+ }
+
+ /**
+ * Release the constructed window context.
+ */
+ private void releaseContext() {
+ mBroadcastDispatcher.unregisterReceiver(mCopyBroadcastReceiver);
+ mContext.release();
+ }
+
+ private void releaseMediaPlayer() {
+ if (mScreenshotSoundController == null) return;
+ mScreenshotSoundController.releaseScreenshotSoundAsync();
+ }
+
+ /**
+ * Update resources on configuration change. Reinflate for theme/color changes.
+ */
+ private void reloadAssets() {
+ if (DEBUG_UI) {
+ Log.d(TAG, "reloadAssets()");
+ }
+
+ mMessageContainerController.setView(mViewProxy.getView());
+ mViewProxy.setCallbacks(new ScreenshotShelfViewProxy.ScreenshotViewCallback() {
+ @Override
+ public void onUserInteraction() {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "onUserInteraction");
+ }
+ mScreenshotHandler.resetTimeout();
+ }
+
+ @Override
+ public void onDismiss() {
+ finishDismiss();
+ }
+
+ @Override
+ public void onTouchOutside() {
+ // TODO(159460485): Remove this when focus is handled properly in the system
+ setWindowFocusable(false);
+ }
+ });
+
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "setContentView: " + mViewProxy.getView());
+ }
+ mWindow.setContentView(mViewProxy.getView());
+ }
+
+ private void enqueueScrollCaptureRequest(UUID requestId, UserHandle owner) {
+ // Wait until this window is attached to request because it is
+ // the reference used to locate the target window (below).
+ withWindowAttached(() -> {
+ requestScrollCapture(requestId, owner);
+ mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
+ new ViewRootImpl.ActivityConfigCallback() {
+ @Override
+ public void onConfigurationChanged(Configuration overrideConfig,
+ int newDisplayId) {
+ if (mConfigChanges.applyNewConfig(mContext.getResources())) {
+ // Hide the scroll chip until we know it's available in this
+ // orientation
+ mActionsController.onScrollChipInvalidated();
+ // Delay scroll capture eval a bit to allow the underlying activity
+ // to set up in the new orientation.
+ mScreenshotHandler.postDelayed(
+ () -> requestScrollCapture(requestId, owner), 150);
+ mViewProxy.updateInsets(
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets());
+ // Screenshot animation calculations won't be valid anymore,
+ // so just end
+ if (mScreenshotAnimation != null
+ && mScreenshotAnimation.isRunning()) {
+ mScreenshotAnimation.end();
+ }
+ }
+ }
+
+ @Override
+ public void requestCompatCameraControl(boolean showControl,
+ boolean transformationApplied,
+ ICompatCameraControlCallback callback) {
+ Log.w(TAG, "Unexpected requestCompatCameraControl callback");
+ }
+ });
+ });
+ }
+
+ private void requestScrollCapture(UUID requestId, UserHandle owner) {
+ mScrollCaptureExecutor.requestScrollCapture(
+ mDisplay.getDisplayId(),
+ mWindow.getDecorView().getWindowToken(),
+ (response) -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
+ 0, response.getPackageName());
+ mActionsController.onScrollChipReady(requestId,
+ () -> onScrollButtonClicked(owner, response));
+ return Unit.INSTANCE;
+ }
+ );
+ }
+
+ private void onScrollButtonClicked(UserHandle owner, ScrollCaptureResponse response) {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "scroll chip tapped");
+ }
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
+ response.getPackageName());
+ Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplay.getDisplayId(),
+ getFullScreenRect());
+ if (newScreenshot == null) {
+ Log.e(TAG, "Failed to capture current screenshot for scroll transition!");
+ return;
+ }
+ // delay starting scroll capture to make sure scrim is up before the app moves
+ mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
+ mScreenshotTakenInPortrait, () -> executeBatchScrollCapture(response, owner));
+ }
+
+ private void executeBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
+ mScrollCaptureExecutor.executeBatchScrollCapture(response,
+ () -> {
+ final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent(
+ owner, mContext);
+ mContext.startActivity(intent);
+ },
+ mViewProxy::restoreNonScrollingUi,
+ mViewProxy::startLongScreenshotTransition);
+ }
+
+ private void withWindowAttached(Runnable action) {
+ View decorView = mWindow.getDecorView();
+ if (decorView.isAttachedToWindow()) {
+ action.run();
+ } else {
+ decorView.getViewTreeObserver().addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ mAttachRequested = false;
+ decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
+ action.run();
+ }
+
+ @Override
+ public void onWindowDetached() {
+ }
+ });
+
+ }
+ }
+
+ @MainThread
+ private void attachWindow() {
+ View decorView = mWindow.getDecorView();
+ if (decorView.isAttachedToWindow() || mAttachRequested) {
+ return;
+ }
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "attachWindow");
+ }
+ mAttachRequested = true;
+ mWindowManager.addView(decorView, mWindowLayoutParams);
+ decorView.requestApplyInsets();
+
+ ViewGroup layout = decorView.requireViewById(android.R.id.content);
+ layout.setClipChildren(false);
+ layout.setClipToPadding(false);
+ }
+
+ @Override
+ public void removeWindow() {
+ final View decorView = mWindow.peekDecorView();
+ if (decorView != null && decorView.isAttachedToWindow()) {
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "Removing screenshot window");
+ }
+ mWindowManager.removeViewImmediate(decorView);
+ mDetachRequested = false;
+ }
+ if (mAttachRequested && !mDetachRequested) {
+ mDetachRequested = true;
+ withWindowAttached(this::removeWindow);
+ }
+
+ mViewProxy.stopInputListening();
+ }
+
+ private void playCameraSoundIfNeeded() {
+ if (mScreenshotSoundController == null) return;
+ // the controller is not-null only on the default display controller
+ mScreenshotSoundController.playScreenshotSoundAsync();
+ }
+
+ /**
+ * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
+ * failure).
+ */
+ private void saveScreenshotAndToast(ScreenshotData screenshot, Consumer<Uri> finisher) {
+ // Play the shutter sound to notify that we've taken a screenshot
+ playCameraSoundIfNeeded();
+
+ if (screenshotSaveImageExporter()) {
+ saveScreenshotInBackground(screenshot, UUID.randomUUID(), finisher, result -> {
+ if (result.uri != null) {
+ mScreenshotHandler.post(() -> Toast.makeText(mContext,
+ R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
+ }
+ });
+ } else {
+ saveScreenshotInWorkerThread(
+ screenshot.getUserHandle(),
+ /* onComplete */ finisher,
+ /* actionsReadyListener */ imageData -> {
+ if (DEBUG_CALLBACK) {
+ Log.d(TAG,
+ "returning URI to finisher (Consumer<URI>): " + imageData.uri);
+ }
+ finisher.accept(imageData.uri);
+ if (imageData.uri == null) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0,
+ mPackageName);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_save_text);
+ } else {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
+ mScreenshotHandler.post(() -> Toast.makeText(mContext,
+ R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
+ }
+ },
+ null);
+ }
+ }
+
+ /**
+ * Starts the animation after taking the screenshot
+ */
+ private void startAnimation(Rect screenRect, boolean showFlash, Runnable onAnimationComplete) {
+ if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
+ mScreenshotAnimation.cancel();
+ }
+
+ mScreenshotAnimation =
+ mViewProxy.createScreenshotDropInAnimation(screenRect, showFlash);
+ if (onAnimationComplete != null) {
+ mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ onAnimationComplete.run();
+ }
+ });
+ }
+
+ // Play the shutter sound to notify that we've taken a screenshot
+ playCameraSoundIfNeeded();
+
+ if (DEBUG_ANIM) {
+ Log.d(TAG, "starting post-screenshot animation");
+ }
+ mScreenshotAnimation.start();
+ }
+
+ /** Reset screenshot view and then call onCompleteRunnable */
+ private void finishDismiss() {
+ Log.d(TAG, "finishDismiss");
+ mActionsController.endScreenshotSession();
+ mScrollCaptureExecutor.close();
+ if (mCurrentRequestCallback != null) {
+ mCurrentRequestCallback.onFinish();
+ mCurrentRequestCallback = null;
+ }
+ mViewProxy.reset();
+ removeWindow();
+ mScreenshotHandler.cancelTimeout();
+ }
+
+ private void saveScreenshotInBackground(ScreenshotData screenshot, UUID requestId,
+ Consumer<Uri> finisher, Consumer<ImageExporter.Result> onResult) {
+ ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor,
+ requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(),
+ mDisplay.getDisplayId());
+ future.addListener(() -> {
+ try {
+ ImageExporter.Result result = future.get();
+ Log.d(TAG, "Saved screenshot: " + result);
+ logScreenshotResultStatus(result.uri, screenshot.getUserHandle());
+ onResult.accept(result);
+ if (DEBUG_CALLBACK) {
+ Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) "
+ + "finisher.accept(\"" + result.uri + "\"");
+ }
+ finisher.accept(result.uri);
+ } catch (Exception e) {
+ Log.d(TAG, "Failed to store screenshot", e);
+ if (DEBUG_CALLBACK) {
+ Log.d(TAG, "Calling (Consumer<Uri>) finisher.accept(null)");
+ }
+ finisher.accept(null);
+ }
+ }, mMainExecutor);
+ }
+
+ /**
+ * Creates a new worker thread and saves the screenshot to the media store.
+ */
+ private void saveScreenshotInWorkerThread(
+ UserHandle owner,
+ @NonNull Consumer<Uri> finisher,
+ @Nullable SaveImageInBackgroundTask.ActionsReadyListener actionsReadyListener,
+ @Nullable SaveImageInBackgroundTask.QuickShareActionReadyListener
+ quickShareActionsReadyListener) {
+ SaveImageInBackgroundTask.SaveImageInBackgroundData
+ data = new SaveImageInBackgroundTask.SaveImageInBackgroundData();
+ data.image = mScreenBitmap;
+ data.finisher = finisher;
+ data.mActionsReadyListener = actionsReadyListener;
+ data.mQuickShareActionsReadyListener = quickShareActionsReadyListener;
+ data.owner = owner;
+ data.displayId = mDisplay.getDisplayId();
+
+ if (mSaveInBgTask != null) {
+ // just log success/failure for the pre-existing screenshot
+ mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
+ }
+
+ mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter,
+ mScreenshotSmartActions, data,
+ mScreenshotNotificationSmartActionsProvider);
+ mSaveInBgTask.execute();
+ }
+
+ /**
+ * Logs success/failure of the screenshot saving task, and shows an error if it failed.
+ */
+ private void logScreenshotResultStatus(Uri uri, UserHandle owner) {
+ if (uri == null) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_save_text);
+ } else {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
+ if (mUserManager.isManagedProfile(owner.getIdentifier())) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0,
+ mPackageName);
+ }
+ }
+ }
+
+ /**
+ * Logs success/failure of the screenshot saving task, and shows an error if it failed.
+ */
+ private void logSuccessOnActionsReady(SaveImageInBackgroundTask.SavedImageData imageData) {
+ logScreenshotResultStatus(imageData.uri, imageData.owner);
+ }
+
+ private boolean isUserSetupComplete(UserHandle owner) {
+ return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
+ .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+ }
+
+ /**
+ * Updates the window focusability. If the window is already showing, then it updates the
+ * window immediately, otherwise the layout params will be applied when the window is next
+ * shown.
+ */
+ private void setWindowFocusable(boolean focusable) {
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "setWindowFocusable: " + focusable);
+ }
+ int flags = mWindowLayoutParams.flags;
+ if (focusable) {
+ mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ } else {
+ mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ }
+ if (mWindowLayoutParams.flags == flags) {
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "setWindowFocusable: skipping, already " + focusable);
+ }
+ return;
+ }
+ final View decorView = mWindow.peekDecorView();
+ if (decorView != null && decorView.isAttachedToWindow()) {
+ mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
+ }
+ }
+
+ private Rect getFullScreenRect() {
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ mDisplay.getRealMetrics(displayMetrics);
+ return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
+ }
+
+ /** Does the aspect ratio of the bitmap with insets removed match the bounds. */
+ private static boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets,
+ Rect screenBounds) {
+ int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right;
+ int insettedHeight = bitmap.getHeight() - bitmapInsets.top - bitmapInsets.bottom;
+
+ if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0
+ || bitmap.getHeight() == 0) {
+ if (DEBUG_UI) {
+ Log.e(TAG, "Provided bitmap and insets create degenerate region: "
+ + bitmap.getWidth() + "x" + bitmap.getHeight() + " " + bitmapInsets);
+ }
+ return false;
+ }
+
+ float insettedBitmapAspect = ((float) insettedWidth) / insettedHeight;
+ float boundsAspect = ((float) screenBounds.width()) / screenBounds.height();
+
+ boolean matchWithinTolerance = Math.abs(insettedBitmapAspect - boundsAspect) < 0.1f;
+ if (DEBUG_UI) {
+ Log.d(TAG, "aspectRatiosMatch: don't match bitmap: " + insettedBitmapAspect
+ + ", bounds: " + boundsAspect);
+ }
+ return matchWithinTolerance;
+ }
+
+ /** Injectable factory to create screenshot controller instances for a specific display. */
+ @AssistedFactory
+ public interface Factory extends InteractiveScreenshotHandler.Factory {
+ /**
+ * Creates an instance of the controller for that specific display.
+ *
+ * @param display display to capture
+ */
+ LegacyScreenshotController create(Display display);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 54ae225..9bc3bd8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -286,7 +286,7 @@
ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
Intent intent = new Intent(context, SmartActionsReceiver.class)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, action.actionIntent)
+ .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT, action.actionIntent)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
@@ -302,9 +302,9 @@
private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
boolean smartActionsEnabled) {
intent
- .putExtra(ScreenshotController.EXTRA_ACTION_TYPE, actionType)
- .putExtra(ScreenshotController.EXTRA_ID, screenshotId)
- .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
+ .putExtra(SmartActionsReceiver.EXTRA_ACTION_TYPE, actionType)
+ .putExtra(SmartActionsReceiver.EXTRA_ID, screenshotId)
+ .putExtra(SmartActionsReceiver.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
}
/**
@@ -327,8 +327,8 @@
}
Intent wrappedIntent = new Intent(mContext, SmartActionsReceiver.class)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT_FILLIN,
+ .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT, quickShare.actionIntent)
+ .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT_FILLIN,
createFillInIntent(uri, imageTime))
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
Bundle extras = quickShare.getExtras();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 0a4635e..653e49f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -95,28 +95,9 @@
/**
* Controls the state and flow for screenshots.
*/
-public class ScreenshotController implements ScreenshotHandler {
+public class ScreenshotController implements InteractiveScreenshotHandler {
private static final String TAG = logTag(ScreenshotController.class);
- public interface TransitionDestination {
- /**
- * Allows the long screenshot activity to call back with a destination location (the bounds
- * on screen of the destination for the transitioning view) and a Runnable to be run once
- * the transition animation is complete.
- */
- void setTransitionDestination(Rect transitionDestination, Runnable onTransitionEnd);
- }
-
- // These strings are used for communicating the action invoked to
- // ScreenshotNotificationSmartActionsProvider.
- public static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
- public static final String EXTRA_ID = "android:screenshot_id";
- public static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
- public static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
- public static final String EXTRA_ACTION_INTENT_FILLIN =
- "android:screenshot_action_intent_fillin";
-
-
// From WizardManagerHelper.java
private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
@@ -378,9 +359,7 @@
if (screenshotPrivateProfileAccessibilityAnnouncementFix()) {
mAnnouncementResolver.getScreenshotAnnouncement(
screenshot.getUserHandle().getIdentifier(),
- announcement -> {
- mViewProxy.announceForAccessibility(announcement);
- });
+ mViewProxy::announceForAccessibility);
} else {
if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
mViewProxy.announceForAccessibility(mContext.getResources().getString(
@@ -413,16 +392,19 @@
* Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already
* being dismissed)
*/
- void requestDismissal(ScreenshotEvent event) {
+ @Override
+ public void requestDismissal(ScreenshotEvent event) {
mViewProxy.requestDismissal(event);
}
- boolean isPendingSharedTransition() {
+ @Override
+ public boolean isPendingSharedTransition() {
return mActionExecutor.isPendingSharedTransition();
}
// Any cleanup needed when the service is being destroyed.
- void onDestroy() {
+ @Override
+ public void onDestroy() {
if (mSaveInBgTask != null) {
// just log success/failure for the pre-existing screenshot
mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
@@ -603,7 +585,8 @@
layout.setClipToPadding(false);
}
- void removeWindow() {
+ @Override
+ public void removeWindow() {
final View decorView = mWindow.peekDecorView();
if (decorView != null && decorView.isAttachedToWindow()) {
if (DEBUG_WINDOW) {
@@ -854,12 +837,12 @@
/** Injectable factory to create screenshot controller instances for a specific display. */
@AssistedFactory
- public interface Factory {
+ public interface Factory extends InteractiveScreenshotHandler.Factory {
/**
* Creates an instance of the controller for that specific display.
*
* @param display display to capture
*/
- ScreenshotController create(Display display);
+ LegacyScreenshotController create(Display display);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
index f8b22a6..f902693 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
@@ -17,10 +17,6 @@
package com.android.systemui.screenshot;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT_FILLIN;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_TYPE;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
import android.app.ActivityOptions;
import android.app.PendingIntent;
@@ -36,6 +32,15 @@
*/
public class SmartActionsReceiver extends BroadcastReceiver {
private static final String TAG = "SmartActionsReceiver";
+ // These strings are used for communicating the action invoked to
+ // ScreenshotNotificationSmartActionsProvider.
+ public static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
+ public static final String EXTRA_ID = "android:screenshot_id";
+ public static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
+ public static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
+ public static final String EXTRA_ACTION_INTENT_FILLIN =
+ "android:screenshot_action_intent_fillin";
+
private final ScreenshotSmartActions mScreenshotSmartActions;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 07f6e85..50ea3bb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -74,7 +74,7 @@
class TakeScreenshotExecutorImpl
@Inject
constructor(
- private val screenshotControllerFactory: ScreenshotController.Factory,
+ private val interactiveScreenshotHandlerFactory: InteractiveScreenshotHandler.Factory,
displayRepository: DisplayRepository,
@Application private val mainScope: CoroutineScope,
private val screenshotRequestProcessor: ScreenshotRequestProcessor,
@@ -83,7 +83,7 @@
private val headlessScreenshotHandler: HeadlessScreenshotHandler,
) : TakeScreenshotExecutor {
private val displays = displayRepository.displays
- private var screenshotController: ScreenshotController? = null
+ private var screenshotController: InteractiveScreenshotHandler? = null
private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>()
/**
@@ -183,7 +183,7 @@
/** Propagates the close system dialog signal to the ScreenshotController. */
override fun onCloseSystemDialogsReceived() {
- if (screenshotController?.isPendingSharedTransition == false) {
+ if (screenshotController?.isPendingSharedTransition() == false) {
screenshotController?.requestDismissal(SCREENSHOT_DISMISSED_OTHER)
}
}
@@ -218,8 +218,9 @@
}
}
- private fun getScreenshotController(display: Display): ScreenshotController {
- val controller = screenshotController ?: screenshotControllerFactory.create(display)
+ private fun getScreenshotController(display: Display): InteractiveScreenshotHandler {
+ val controller =
+ screenshotController ?: interactiveScreenshotHandlerFactory.create(display)
screenshotController = controller
return controller
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
index 8feefa4..9db1f24 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -46,13 +46,17 @@
import android.os.ResultReceiver;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
+import android.widget.ListPopupWindow;
import android.widget.TextView;
import androidx.activity.ComponentActivity;
import androidx.annotation.Nullable;
+import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
@@ -67,6 +71,7 @@
import com.android.systemui.screenshot.scroll.CropView;
import com.android.systemui.settings.UserTracker;
+import java.util.List;
import java.util.Set;
import javax.inject.Inject;
@@ -92,6 +97,7 @@
private static final String TAG = AppClipsActivity.class.getSimpleName();
private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0);
+ private static final int DRAWABLE_END = 2;
private final AppClipsViewModel.Factory mViewModelFactory;
private final PackageManager mPackageManager;
@@ -192,6 +198,7 @@
mViewModel.getResultLiveData().observe(this, this::setResultThenFinish);
mViewModel.getErrorLiveData().observe(this, this::setErrorThenFinish);
mViewModel.getBacklinksLiveData().observe(this, this::setBacklinksData);
+ mViewModel.mSelectedBacklinksLiveData.observe(this, this::updateBacklinksTextView);
if (savedInstanceState == null) {
int displayId = getDisplayId();
@@ -305,8 +312,8 @@
if (mBacklinksIncludeDataCheckBox.getVisibility() == View.VISIBLE
&& mBacklinksIncludeDataCheckBox.isChecked()
- && mViewModel.getBacklinksLiveData().getValue() != null) {
- ClipData backlinksData = mViewModel.getBacklinksLiveData().getValue().getClipData();
+ && mViewModel.mSelectedBacklinksLiveData.getValue() != null) {
+ ClipData backlinksData = mViewModel.mSelectedBacklinksLiveData.getValue().getClipData();
data.putParcelable(EXTRA_CLIP_DATA, backlinksData);
DebugLogger.INSTANCE.logcatMessage(this,
@@ -330,18 +337,80 @@
finish();
}
- private void setBacklinksData(InternalBacklinksData backlinksData) {
+ private void setBacklinksData(List<InternalBacklinksData> backlinksData) {
mBacklinksIncludeDataCheckBox.setVisibility(View.VISIBLE);
mBacklinksDataTextView.setVisibility(
mBacklinksIncludeDataCheckBox.isChecked() ? View.VISIBLE : View.GONE);
- mBacklinksDataTextView.setText(backlinksData.getClipData().getDescription().getLabel());
+ // Set up the dropdown when multiple backlinks are available.
+ if (backlinksData.size() > 1) {
+ setUpListPopupWindow(backlinksData, mBacklinksDataTextView);
+ }
+ }
+ private void setUpListPopupWindow(List<InternalBacklinksData> backlinksData, View anchor) {
+ ListPopupWindow listPopupWindow = new ListPopupWindow(this);
+ listPopupWindow.setAnchorView(anchor);
+ listPopupWindow.setOverlapAnchor(true);
+ listPopupWindow.setBackgroundDrawable(
+ AppCompatResources.getDrawable(this, R.drawable.backlinks_rounded_rectangle));
+ listPopupWindow.setOnItemClickListener((parent, view, position, id) -> {
+ mViewModel.mSelectedBacklinksLiveData.setValue(backlinksData.get(position));
+ listPopupWindow.dismiss();
+ });
+
+ ArrayAdapter<InternalBacklinksData> adapter = new ArrayAdapter<>(this,
+ R.layout.app_clips_backlinks_drop_down_entry) {
+ @Override
+ public View getView(int position, @Nullable View convertView, ViewGroup parent) {
+ TextView itemView = (TextView) super.getView(position, convertView, parent);
+ InternalBacklinksData data = backlinksData.get(position);
+ itemView.setText(data.getClipData().getDescription().getLabel());
+
+ Drawable icon = data.getAppIcon();
+ icon.setBounds(createBacklinksTextViewDrawableBounds());
+ itemView.setCompoundDrawablesRelative(/* start= */ icon, /* top= */ null,
+ /* end= */ null, /* bottom= */ null);
+
+ return itemView;
+ }
+ };
+ adapter.addAll(backlinksData);
+ listPopupWindow.setAdapter(adapter);
+
+ mBacklinksDataTextView.setOnClickListener(unused -> listPopupWindow.show());
+ }
+
+ /**
+ * Updates the {@link #mBacklinksDataTextView} with the currently selected
+ * {@link InternalBacklinksData}. The {@link AppClipsViewModel#getBacklinksLiveData()} is
+ * expected to be already set when this method is called.
+ */
+ private void updateBacklinksTextView(InternalBacklinksData backlinksData) {
+ mBacklinksDataTextView.setText(backlinksData.getClipData().getDescription().getLabel());
Drawable appIcon = backlinksData.getAppIcon();
- int size = getResources().getDimensionPixelSize(R.dimen.appclips_backlinks_icon_size);
- appIcon.setBounds(/* left= */ 0, /* top= */ 0, /* right= */ size, /* bottom= */ size);
+ Rect compoundDrawableBounds = createBacklinksTextViewDrawableBounds();
+ appIcon.setBounds(compoundDrawableBounds);
+
+ // Try to reuse the dropdown down arrow icon if available, will be null if never set.
+ Drawable dropDownIcon = mBacklinksDataTextView.getCompoundDrawablesRelative()[DRAWABLE_END];
+ if (mViewModel.getBacklinksLiveData().getValue().size() > 1 && dropDownIcon == null) {
+ // Set up the dropdown down arrow drawable only if it is required.
+ dropDownIcon = AppCompatResources.getDrawable(this, R.drawable.arrow_pointing_down);
+ dropDownIcon.setBounds(compoundDrawableBounds);
+ dropDownIcon.setTint(Utils.getColorAttr(this,
+ android.R.attr.textColorSecondary).getDefaultColor());
+ }
+
mBacklinksDataTextView.setCompoundDrawablesRelative(/* start= */ appIcon, /* top= */
- null, /* end= */ null, /* bottom= */ null);
+ null, /* end= */ dropDownIcon, /* bottom= */ null);
+ }
+
+ private Rect createBacklinksTextViewDrawableBounds() {
+ int size = getResources().getDimensionPixelSize(R.dimen.appclips_backlinks_icon_size);
+ Rect bounds = new Rect();
+ bounds.set(/* left= */ 0, /* top= */ 0, /* right= */ size, /* bottom= */ size);
+ return bounds;
}
private void setError(int errorCode) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
index bd9e295..3530b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
@@ -21,12 +21,11 @@
import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
import static android.content.Intent.CATEGORY_LAUNCHER;
-import static com.google.common.util.concurrent.Futures.withTimeout;
-
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IActivityTaskManager;
+import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.app.assist.AssistContent;
import android.content.ClipData;
@@ -41,7 +40,6 @@
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.Display;
@@ -94,7 +92,8 @@
private final MutableLiveData<Bitmap> mScreenshotLiveData;
private final MutableLiveData<Uri> mResultLiveData;
private final MutableLiveData<Integer> mErrorLiveData;
- private final MutableLiveData<InternalBacklinksData> mBacklinksLiveData;
+ private final MutableLiveData<List<InternalBacklinksData>> mBacklinksLiveData;
+ final MutableLiveData<InternalBacklinksData> mSelectedBacklinksLiveData;
private AppClipsViewModel(AppClipsCrossProcessHelper appClipsCrossProcessHelper,
ImageExporter imageExporter, IActivityTaskManager atmService,
@@ -112,6 +111,7 @@
mResultLiveData = new MutableLiveData<>();
mErrorLiveData = new MutableLiveData<>();
mBacklinksLiveData = new MutableLiveData<>();
+ mSelectedBacklinksLiveData = new MutableLiveData<>();
}
/**
@@ -135,10 +135,11 @@
/**
* Triggers the Backlinks flow which:
* <ul>
- * <li>Evaluates the task to query.
- * <li>Requests {@link AssistContent} from that task.
- * <li>Transforms the {@link AssistContent} into {@link ClipData} for Backlinks.
- * <li>The {@link ClipData} is reported to activity via {@link #getBacklinksLiveData()}.
+ * <li>Evaluates the tasks to query.
+ * <li>Requests {@link AssistContent} from all valid tasks.
+ * <li>Transforms {@link AssistContent} into {@link InternalBacklinksData} for Backlinks.
+ * <li>The {@link InternalBacklinksData}s are reported to activity via
+ * {@link #getBacklinksLiveData()}.
* </ul>
*
* @param taskIdsToIgnore id of the tasks to ignore when querying for {@link AssistContent}
@@ -146,24 +147,24 @@
*/
void triggerBacklinks(Set<Integer> taskIdsToIgnore, int displayId) {
DebugLogger.INSTANCE.logcatMessage(this, () -> "Backlinks triggered");
- mBgExecutor.execute(() -> {
- ListenableFuture<InternalBacklinksData> backlinksData = getBacklinksData(
- taskIdsToIgnore, displayId);
- Futures.addCallback(backlinksData, new FutureCallback<>() {
- @Override
- public void onSuccess(@Nullable InternalBacklinksData result) {
- if (result != null) {
- mBacklinksLiveData.setValue(result);
- }
+ ListenableFuture<List<InternalBacklinksData>> backlinksData = getAllAvailableBacklinks(
+ taskIdsToIgnore, displayId);
+ Futures.addCallback(backlinksData, new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable List<InternalBacklinksData> result) {
+ if (result != null && !result.isEmpty()) {
+ // Set the list of backlinks before setting the selected backlink as this is
+ // required when updating the backlink data text view.
+ mBacklinksLiveData.setValue(result);
+ mSelectedBacklinksLiveData.setValue(result.get(0));
}
+ }
- @Override
- public void onFailure(Throwable t) {
- Log.e(TAG, "Error querying for Backlinks data", t);
- }
- }, mMainExecutor);
-
- });
+ @Override
+ public void onFailure(Throwable t) {
+ Log.e(TAG, "Error querying for Backlinks data", t);
+ }
+ }, mMainExecutor);
}
/** Returns a {@link LiveData} that holds the captured screenshot. */
@@ -184,8 +185,11 @@
return mErrorLiveData;
}
- /** Returns a {@link LiveData} that holds Backlinks data in {@link InternalBacklinksData}. */
- LiveData<InternalBacklinksData> getBacklinksLiveData() {
+ /**
+ * Returns a {@link LiveData} that holds all the available Backlinks data and the currently
+ * selected index for displaying the Backlinks in the UI.
+ */
+ LiveData<List<InternalBacklinksData>> getBacklinksLiveData() {
return mBacklinksLiveData;
}
@@ -230,26 +234,58 @@
return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
}
- private ListenableFuture<InternalBacklinksData> getBacklinksData(Set<Integer> taskIdsToIgnore,
- int displayId) {
- return getAllRootTaskInfosOnDisplay(displayId)
- .stream()
- .filter(taskInfo -> shouldIncludeTask(taskInfo, taskIdsToIgnore))
- .findFirst()
- .map(this::getBacklinksDataForTaskId)
- .orElse(Futures.immediateFuture(null));
+ private ListenableFuture<List<InternalBacklinksData>> getAllAvailableBacklinks(
+ Set<Integer> taskIdsToIgnore, int displayId) {
+ ListenableFuture<List<TaskInfo>> allTasksOnDisplayFuture = getAllTasksOnDisplay(displayId);
+
+ ListenableFuture<List<ListenableFuture<InternalBacklinksData>>> backlinksNestedListFuture =
+ Futures.transform(allTasksOnDisplayFuture, allTasksOnDisplay ->
+ allTasksOnDisplay
+ .stream()
+ .filter(taskInfo -> shouldIncludeTask(taskInfo, taskIdsToIgnore))
+ .map(this::getBacklinksDataForTaskInfo)
+ .toList(),
+ mBgExecutor);
+
+ return Futures.transformAsync(backlinksNestedListFuture, Futures::allAsList, mBgExecutor);
}
- private List<RootTaskInfo> getAllRootTaskInfosOnDisplay(int displayId) {
- try {
- return mAtmService.getAllRootTaskInfosOnDisplay(displayId);
- } catch (RemoteException e) {
- Log.e(TAG, String.format("Error while querying for tasks on display %d", displayId), e);
- return Collections.emptyList();
- }
+ /**
+ * Returns all tasks on a given display after querying {@link IActivityTaskManager} from the
+ * {@link #mBgExecutor}.
+ */
+ private ListenableFuture<List<TaskInfo>> getAllTasksOnDisplay(int displayId) {
+ SettableFuture<List<TaskInfo>> recentTasksFuture = SettableFuture.create();
+ mBgExecutor.execute(() -> {
+ try {
+ // Directly call into ActivityTaskManagerService instead of going through WMShell
+ // because WMShell is only available in the main SysUI process and App Clips runs
+ // in its own separate process as it deals with bitmaps.
+ List<TaskInfo> allTasksOnDisplay = mAtmService.getTasks(
+ /* maxNum= */ Integer.MAX_VALUE,
+ // PIP tasks are not visible in recents. So _not_ filtering for
+ // tasks that are only visible in recents.
+ /* filterOnlyVisibleRecents= */ false,
+ /* keepIntentExtra= */ false,
+ displayId)
+ .stream()
+ .map(runningTaskInfo -> (TaskInfo) runningTaskInfo)
+ .toList();
+ recentTasksFuture.set(allTasksOnDisplay);
+ } catch (Exception e) {
+ Log.e(TAG, String.format("Error getting all tasks on displayId %d", displayId), e);
+ recentTasksFuture.set(Collections.emptyList());
+ }
+ });
+
+ return withTimeout(recentTasksFuture);
}
- private boolean shouldIncludeTask(RootTaskInfo taskInfo, Set<Integer> taskIdsToIgnore) {
+ /**
+ * Returns whether the app represented by the provided {@link TaskInfo} should be included for
+ * querying for {@link AssistContent}.
+ */
+ private boolean shouldIncludeTask(TaskInfo taskInfo, Set<Integer> taskIdsToIgnore) {
DebugLogger.INSTANCE.logcatMessage(this,
() -> String.format("shouldIncludeTask taskId %d; topActivity %s", taskInfo.taskId,
taskInfo.topActivity));
@@ -262,11 +298,14 @@
&& taskInfo.numActivities > 0
&& taskInfo.topActivity != null
&& taskInfo.topActivityInfo != null
- && taskInfo.childTaskIds.length > 0
&& taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_STANDARD
&& canAppStartThroughLauncher(taskInfo.topActivity.getPackageName());
}
+ /**
+ * Returns whether the app represented by the provided {@code packageName} can be launched
+ * through the all apps tray by a user.
+ */
private boolean canAppStartThroughLauncher(String packageName) {
// Use Intent.resolveActivity API to check if the intent resolves as that is what Android
// uses internally when apps use Context.startActivity.
@@ -274,8 +313,12 @@
!= null;
}
- private ListenableFuture<InternalBacklinksData> getBacklinksDataForTaskId(
- RootTaskInfo taskInfo) {
+ /**
+ * Returns an {@link InternalBacklinksData} that represents the Backlink data internally, which
+ * is captured by querying the system using {@link TaskInfo#taskId}.
+ */
+ private ListenableFuture<InternalBacklinksData> getBacklinksDataForTaskInfo(
+ TaskInfo taskInfo) {
DebugLogger.INSTANCE.logcatMessage(this,
() -> String.format("getBacklinksDataForTaskId for taskId %d; topActivity %s",
taskInfo.taskId, taskInfo.topActivity));
@@ -284,7 +327,13 @@
int taskId = taskInfo.taskId;
mAssistContentRequester.requestAssistContent(taskId, assistContent ->
backlinksData.set(getBacklinksDataFromAssistContent(taskInfo, assistContent)));
- return withTimeout(backlinksData, 5L, TimeUnit.SECONDS, newSingleThreadScheduledExecutor());
+ return withTimeout(backlinksData);
+ }
+
+ /** Returns the same {@link ListenableFuture} but with a 5 {@link TimeUnit#SECONDS} timeout. */
+ private static <V> ListenableFuture<V> withTimeout(ListenableFuture<V> future) {
+ return Futures.withTimeout(future, 5L, TimeUnit.SECONDS,
+ newSingleThreadScheduledExecutor());
}
/**
@@ -306,7 +355,7 @@
* @param content the {@link AssistContent} to map into Backlinks {@link ClipData}.
* @return {@link InternalBacklinksData} that represents the Backlinks data along with app icon.
*/
- private InternalBacklinksData getBacklinksDataFromAssistContent(RootTaskInfo taskInfo,
+ private InternalBacklinksData getBacklinksDataFromAssistContent(TaskInfo taskInfo,
@Nullable AssistContent content) {
DebugLogger.INSTANCE.logcatMessage(this,
() -> String.format("getBacklinksDataFromAssistContent taskId %d; topActivity %s",
@@ -365,7 +414,7 @@
return resolvedComponent.getPackageName().equals(requiredPackageName);
}
- private String getAppNameOfTask(RootTaskInfo taskInfo) {
+ private String getAppNameOfTask(TaskInfo taskInfo) {
return taskInfo.topActivityInfo.loadLabel(mPackageManager).toString();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 682f848..254dde4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -16,12 +16,17 @@
package com.android.systemui.screenshot.dagger;
+import static com.android.systemui.Flags.screenshotUiControllerRefactor;
+
import android.app.Service;
import android.view.accessibility.AccessibilityManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
+import com.android.systemui.screenshot.InteractiveScreenshotHandler;
+import com.android.systemui.screenshot.LegacyScreenshotController;
+import com.android.systemui.screenshot.ScreenshotController;
import com.android.systemui.screenshot.ScreenshotPolicy;
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
import com.android.systemui.screenshot.ScreenshotSoundController;
@@ -90,4 +95,15 @@
AccessibilityManager accessibilityManager) {
return new ScreenshotViewModel(accessibilityManager);
}
+
+ @Provides
+ static InteractiveScreenshotHandler.Factory providesScreenshotController(
+ LegacyScreenshotController.Factory legacyScreenshotController,
+ ScreenshotController.Factory screenshotController) {
+ if (screenshotUiControllerRefactor()) {
+ return screenshotController;
+ } else {
+ return legacyScreenshotController;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java
index ebac5bf..08c1fca 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java
@@ -16,8 +16,9 @@
package com.android.systemui.screenshot.scroll;
+import android.graphics.Rect;
+
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.screenshot.ScreenshotController;
import java.util.concurrent.atomic.AtomicReference;
@@ -30,9 +31,18 @@
@SysUISingleton
public class LongScreenshotData {
private final AtomicReference<ScrollCaptureController.LongScreenshot> mLongScreenshot;
- private final AtomicReference<ScreenshotController.TransitionDestination>
+ private final AtomicReference<TransitionDestination>
mTransitionDestinationCallback;
+ public interface TransitionDestination {
+ /**
+ * Allows the long screenshot activity to call back with a destination location (the bounds
+ * on screen of the destination for the transitioning view) and a Runnable to be run once
+ * the transition animation is complete.
+ */
+ void setTransitionDestination(Rect transitionDestination, Runnable onTransitionEnd);
+ }
+
@Inject
public LongScreenshotData() {
mLongScreenshot = new AtomicReference<>();
@@ -63,15 +73,14 @@
/**
* Set the holder's TransitionDestination callback.
*/
- public void setTransitionDestinationCallback(
- ScreenshotController.TransitionDestination destination) {
+ public void setTransitionDestinationCallback(TransitionDestination destination) {
mTransitionDestinationCallback.set(destination);
}
/**
* Return the current TransitionDestination callback and clear.
*/
- public ScreenshotController.TransitionDestination takeTransitionDestinationCallback() {
+ public TransitionDestination takeTransitionDestinationCallback() {
return mTransitionDestinationCallback.getAndSet(null);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index b468d0e..05c50fe 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -39,6 +39,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Flags
import com.android.systemui.Flags.glanceableHubBackGesture
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
@@ -132,13 +133,6 @@
private var touchMonitor: TouchMonitor? = null
/**
- * The width of the area in which a right edge swipe can open the hub, in pixels. Read from
- * resources when [initView] is called.
- */
- // TODO(b/320786721): support RTL layouts
- private var rightEdgeSwipeRegionWidth: Int = 0
-
- /**
* True if we are currently tracking a touch intercepted by the hub, either because the hub is
* open or being opened.
*/
@@ -264,11 +258,6 @@
communalContainerView = containerView
- rightEdgeSwipeRegionWidth =
- containerView.resources.getDimensionPixelSize(
- R.dimen.communal_right_edge_swipe_region_width
- )
-
val topEdgeSwipeRegionWidth =
containerView.resources.getDimensionPixelSize(
R.dimen.communal_top_edge_swipe_region_height
@@ -285,7 +274,7 @@
// Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and not
// occluded.
lifecycleRegistry.repeatOnLifecycle(Lifecycle.State.RESUMED) {
- // Avoid adding exclusion to right/left edges to allow back gestures.
+ // Avoid adding exclusion to end/start edges to allow back gestures.
val insets =
if (glanceableHubBackGesture()) {
containerView.rootWindowInsets.getInsets(WindowInsets.Type.systemGestures())
@@ -293,25 +282,38 @@
Insets.NONE
}
- containerView.systemGestureExclusionRects =
- listOf(
- // Only allow swipe up to bouncer and swipe down to shade in the very
- // top/bottom to avoid conflicting with widgets in the hub grid.
- Rect(
- insets.left,
- topEdgeSwipeRegionWidth,
- containerView.right - insets.right,
- containerView.bottom - bottomEdgeSwipeRegionWidth
- ),
- // Disable back gestures on the left side of the screen, to avoid
- // conflicting with scene transitions.
- Rect(
- 0,
- 0,
- insets.right,
- containerView.bottom,
- )
+ val ltr = containerView.layoutDirection == View.LAYOUT_DIRECTION_LTR
+
+ val backGestureInset =
+ Rect(
+ if (ltr) 0 else insets.left,
+ 0,
+ if (ltr) insets.right else containerView.right,
+ containerView.bottom,
)
+
+ containerView.systemGestureExclusionRects =
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ listOf(
+ // Disable back gestures on the left side of the screen, to avoid
+ // conflicting with scene transitions.
+ backGestureInset
+ )
+ } else {
+ listOf(
+ // Only allow swipe up to bouncer and swipe down to shade in the very
+ // top/bottom to avoid conflicting with widgets in the hub grid.
+ Rect(
+ insets.left,
+ topEdgeSwipeRegionWidth,
+ containerView.right - insets.right,
+ containerView.bottom - bottomEdgeSwipeRegionWidth
+ ),
+ // Disable back gestures on the left side of the screen, to avoid
+ // conflicting with scene transitions.
+ backGestureInset
+ )
+ }
}
}
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/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 2b2aac64..f90dd3c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -24,8 +24,8 @@
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.activatable.Activatable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -41,22 +41,23 @@
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/** Models UI state and handles user input for the shade scene. */
@SysUISingleton
class ShadeSceneViewModel
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
val qsSceneAdapter: QSSceneAdapter,
val shadeHeaderViewModel: ShadeHeaderViewModel,
val brightnessMirrorViewModel: BrightnessMirrorViewModel,
@@ -66,42 +67,61 @@
private val footerActionsController: FooterActionsController,
private val sceneInteractor: SceneInteractor,
private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
-) {
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+) : Activatable {
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
combine(
- shadeInteractor.shadeMode,
- qsSceneAdapter.isCustomizerShowing,
- ) { shadeMode, isCustomizerShowing ->
- destinationScenes(
- shadeMode = shadeMode,
- isCustomizing = isCustomizerShowing,
- )
+ shadeInteractor.shadeMode,
+ qsSceneAdapter.isCustomizerShowing,
+ ) { shadeMode, isCustomizerShowing ->
+ buildMap {
+ if (!isCustomizerShowing) {
+ set(
+ Swipe(SwipeDirection.Up),
+ UserActionResult(
+ SceneFamilies.Home,
+ ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+ )
+ )
+ }
+
+ // TODO(b/330200163) Add an else to be able to collapse the shade while customizing
+ if (shadeMode is ShadeMode.Single) {
+ set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings))
+ }
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- destinationScenes(
- shadeMode = shadeInteractor.shadeMode.value,
- isCustomizing = qsSceneAdapter.isCustomizerShowing.value,
- ),
- )
+ }
private val upDestinationSceneKey: Flow<SceneKey?> =
destinationScenes.map { it[Swipe(SwipeDirection.Up)]?.toScene }
+ private val _isClickable = MutableStateFlow(false)
/** Whether or not the shade container should be clickable. */
- val isClickable: StateFlow<Boolean> =
- upDestinationSceneKey
- .flatMapLatestConflated { key ->
- key?.let { sceneInteractor.resolveSceneFamily(key) } ?: flowOf(null)
+ val isClickable: StateFlow<Boolean> = _isClickable.asStateFlow()
+
+ /**
+ * Activates the view-model.
+ *
+ * Serves as an entrypoint to kick off coroutine work that the view-model requires in order to
+ * keep its state fresh and/or perform side-effects.
+ *
+ * Suspends the caller forever as it will keep doing work until canceled.
+ *
+ * **Must be invoked** when the scene becomes the current scene or when it becomes visible
+ * during a transition (the choice is the responsibility of the parent). Similarly, the work
+ * must be canceled when the scene stops being visible or the current scene.
+ */
+ override suspend fun activate() {
+ coroutineScope {
+ launch {
+ upDestinationSceneKey
+ .flatMapLatestConflated { key ->
+ key?.let { sceneInteractor.resolveSceneFamily(key) } ?: flowOf(null)
+ }
+ .map { it == Scenes.Lockscreen }
+ .collectLatest { _isClickable.value = it }
}
- .map { it == Scenes.Lockscreen }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false
- )
+ }
+ }
val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
@@ -132,24 +152,4 @@
}
return footerActionsViewModelFactory.create(lifecycleOwner)
}
-
- private fun destinationScenes(
- shadeMode: ShadeMode,
- isCustomizing: Boolean,
- ): Map<UserAction, UserActionResult> {
- return buildMap {
- if (!isCustomizing) {
- set(
- Swipe(SwipeDirection.Up),
- UserActionResult(
- SceneFamilies.Home,
- ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
- )
- )
- } // TODO(b/330200163) Add an else to be able to collapse the shade while customizing
- if (shadeMode is ShadeMode.Single) {
- set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings))
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
index a6605f6..a621b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
@@ -18,12 +18,9 @@
import android.annotation.SuppressLint
import android.app.NotificationManager
-import android.os.UserHandle
-import android.provider.Settings
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -43,23 +40,16 @@
import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printCollection
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.conflate
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
/**
@@ -73,12 +63,10 @@
class LockScreenMinimalismCoordinator
@Inject
constructor(
- @Background private val bgDispatcher: CoroutineDispatcher,
private val dumpManager: DumpManager,
private val headsUpInteractor: HeadsUpNotificationInteractor,
private val logger: LockScreenMinimalismCoordinatorLogger,
@Application private val scope: CoroutineScope,
- private val secureSettings: SecureSettings,
private val seenNotificationsInteractor: SeenNotificationsInteractor,
private val statusBarStateController: StatusBarStateController,
private val shadeInteractor: ShadeInteractor,
@@ -147,29 +135,7 @@
if (NotificationMinimalismPrototype.isEnabled) {
return flowOf(true)
}
- return secureSettings
- // emit whenever the setting has changed
- .observerFlow(
- UserHandle.USER_ALL,
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- )
- // perform a query immediately
- .onStart { emit(Unit) }
- // for each change, lookup the new value
- .map {
- secureSettings.getIntForUser(
- name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- def = 0,
- userHandle = UserHandle.USER_CURRENT,
- ) == 1
- }
- // don't emit anything if nothing has changed
- .distinctUntilChanged()
- // perform lookups on the bg thread pool
- .flowOn(bgDispatcher)
- // only track the most recent emission, if events are happening faster than they can be
- // consumed
- .conflate()
+ return seenNotificationsInteractor.isLockScreenShowOnlyUnseenNotificationsEnabled()
}
private suspend fun trackUnseenFilterSettingChanges() {
@@ -177,6 +143,7 @@
// update local field and invalidate if necessary
if (isSettingEnabled != unseenFilterEnabled) {
unseenFilterEnabled = isSettingEnabled
+ unseenNotifications.clear()
unseenNotifPromoter.invalidateList("unseen setting changed")
}
// if the setting is enabled, then start tracking and filtering unseen notifications
@@ -190,21 +157,21 @@
private val collectionListener =
object : NotifCollectionListener {
override fun onEntryAdded(entry: NotificationEntry) {
- if (!isShadeVisible) {
+ if (unseenFilterEnabled && !isShadeVisible) {
logger.logUnseenAdded(entry.key)
unseenNotifications.add(entry)
}
}
override fun onEntryUpdated(entry: NotificationEntry) {
- if (!isShadeVisible) {
+ if (unseenFilterEnabled && !isShadeVisible) {
logger.logUnseenUpdated(entry.key)
unseenNotifications.add(entry)
}
}
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
- if (unseenNotifications.remove(entry)) {
+ if (unseenFilterEnabled && unseenNotifications.remove(entry)) {
logger.logUnseenRemoved(entry.key)
}
}
@@ -212,6 +179,7 @@
private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return
+ if (!unseenFilterEnabled) return
// Only ever elevate a top unseen notification on keyguard, not even locked shade
if (statusBarStateController.state != StatusBarState.KEYGUARD) {
seenNotificationsInteractor.setTopOngoingNotification(null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
index 0103fff..bfea2ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
@@ -17,12 +17,9 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.annotation.SuppressLint
-import android.os.UserHandle
-import android.provider.Settings
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -43,12 +40,9 @@
import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.asIndenting
import com.android.systemui.util.indentIfPossible
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@@ -56,13 +50,10 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
@@ -79,14 +70,12 @@
class OriginalUnseenKeyguardCoordinator
@Inject
constructor(
- @Background private val bgDispatcher: CoroutineDispatcher,
private val dumpManager: DumpManager,
private val headsUpManager: HeadsUpManager,
private val keyguardRepository: KeyguardRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val logger: KeyguardCoordinatorLogger,
@Application private val scope: CoroutineScope,
- private val secureSettings: SecureSettings,
private val seenNotificationsInteractor: SeenNotificationsInteractor,
private val statusBarStateController: StatusBarStateController,
private val sceneInteractor: SceneInteractor,
@@ -268,29 +257,7 @@
// TODO(b/330387368): should this really just be turned off? If so, hide the setting.
return flowOf(false)
}
- return secureSettings
- // emit whenever the setting has changed
- .observerFlow(
- UserHandle.USER_ALL,
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- )
- // perform a query immediately
- .onStart { emit(Unit) }
- // for each change, lookup the new value
- .map {
- secureSettings.getIntForUser(
- name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- def = 0,
- userHandle = UserHandle.USER_CURRENT,
- ) == 1
- }
- // don't emit anything if nothing has changed
- .distinctUntilChanged()
- // perform lookups on the bg thread pool
- .flowOn(bgDispatcher)
- // only track the most recent emission, if events are happening faster than they can be
- // consumed
- .conflate()
+ return seenNotificationsInteractor.isLockScreenShowOnlyUnseenNotificationsEnabled()
}
private suspend fun trackUnseenFilterSettingChanges() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 0f6f03a..3a2f95e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -263,7 +263,8 @@
private boolean isReorderingAllowed() {
final boolean sleepyAndDozed = mFullyDozed && mSleepy;
- final boolean stackShowing = mPanelExpanded || mLockscreenShowing;
+ final boolean stackShowing = mPanelExpanded
+ || (SceneContainerFlag.isEnabled() && mLockscreenShowing);
return (sleepyAndDozed || !stackShowing || mCommunalShowing) && !mPulsing;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 948a3c2..90a05ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -16,21 +16,35 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import android.os.UserHandle
+import android.provider.Settings
import android.util.IndentingPrintWriter
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
import com.android.systemui.util.printSection
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
/** Interactor for business logic associated with the notification stack. */
@SysUISingleton
class SeenNotificationsInteractor
@Inject
constructor(
+ @Background private val bgDispatcher: CoroutineDispatcher,
private val notificationListRepository: ActiveNotificationListRepository,
+ private val secureSettings: SecureSettings,
) {
/** Are any already-seen notifications currently filtered out of the shade? */
val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
@@ -81,4 +95,29 @@
)
}
}
+
+ fun isLockScreenShowOnlyUnseenNotificationsEnabled(): Flow<Boolean> =
+ secureSettings
+ // emit whenever the setting has changed
+ .observerFlow(
+ UserHandle.USER_ALL,
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ )
+ // perform a query immediately
+ .onStart { emit(Unit) }
+ // for each change, lookup the new value
+ .map {
+ secureSettings.getIntForUser(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ def = 0,
+ userHandle = UserHandle.USER_CURRENT,
+ ) == 1
+ }
+ // don't emit anything if nothing has changed
+ .distinctUntilChanged()
+ // perform lookups on the bg thread pool
+ .flowOn(bgDispatcher)
+ // only track the most recent emission, if events are happening faster than they can be
+ // consumed
+ .conflate()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index e04e0fa..9d09595 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -389,6 +389,7 @@
.setTicker(titleStr)
.setContentTitle(titleStr)
.setContentText(textStr)
+ .setStyle(Notification.BigTextStyle().bigText(textStr))
.setSmallIcon(com.android.systemui.res.R.drawable.ic_settings)
.setCategory(Notification.CATEGORY_SYSTEM)
.setTimeoutAfter(/* one day in ms */ 24 * 60 * 60 * 1000L)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index 35afda7..9f634be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -89,10 +90,59 @@
inflater.inflate(R.layout.status_bar_notification_row, parent, listenerExecutor, this);
}
+ /**
+ * Inflates a new notificationView synchronously.
+ * This method is only for testing-purpose.
+ */
+ @VisibleForTesting
+ public ExpandableNotificationRow inflateSynchronously(@NonNull Context context,
+ @Nullable ViewGroup parent, @NonNull NotificationEntry entry) {
+ final LayoutInflater inflater = new BasicRowInflater(context);
+ inflater.setFactory2(makeRowInflater(entry));
+ final ExpandableNotificationRow inflate = (ExpandableNotificationRow) inflater.inflate(
+ R.layout.status_bar_notification_row,
+ parent /* root */,
+ false /* attachToRoot */);
+ return inflate;
+ }
+
private RowAsyncLayoutInflater makeRowInflater(NotificationEntry entry) {
return new RowAsyncLayoutInflater(entry, mSystemClock, mLogger);
}
+ /**
+ * A {@link LayoutInflater} that is copy of BasicLayoutInflater.
+ */
+ private static class BasicRowInflater extends LayoutInflater {
+ private static final String[] sClassPrefixList =
+ {"android.widget.", "android.webkit.", "android.app."};
+ BasicRowInflater(Context context) {
+ super(context);
+ }
+
+ @Override
+ public LayoutInflater cloneInContext(Context newContext) {
+ return new BasicRowInflater(newContext);
+ }
+
+ @Override
+ protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
+ for (String prefix : sClassPrefixList) {
+ try {
+ View view = createView(name, prefix, attrs);
+ if (view != null) {
+ return view;
+ }
+ } catch (ClassNotFoundException e) {
+ // In this case we want to let the base class take a crack
+ // at it.
+ }
+ }
+
+ return super.onCreateView(name, attrs);
+ }
+ }
+
@VisibleForTesting
public static class RowAsyncLayoutInflater implements AsyncLayoutFactory {
private final NotificationEntry mEntry;
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/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8f2ad40..41b69a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -802,7 +802,10 @@
hideAlternateBouncer(false);
if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
if (SceneContainerFlag.isEnabled()) {
- mDeviceEntryInteractorLazy.get().attemptDeviceEntry();
+ mSceneInteractorLazy.get().changeScene(
+ Scenes.Bouncer,
+ "primary bouncer requested"
+ );
} else {
mPrimaryBouncerInteractor.show(scrimmed);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index a88c6d7..a963826 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -38,9 +38,10 @@
class AvalancheController
@Inject
constructor(
- dumpManager: DumpManager,
- private val uiEventLogger: UiEventLogger,
- @Background private val bgHandler: Handler
+ dumpManager: DumpManager,
+ private val uiEventLogger: UiEventLogger,
+ private val headsUpManagerLogger: HeadsUpManagerLogger,
+ @Background private val bgHandler: Handler
) : Dumpable {
private val tag = "AvalancheController"
@@ -109,32 +110,36 @@
}
/** Run or delay Runnable for given HeadsUpEntry */
- fun update(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+ fun update(entry: HeadsUpEntry?, runnable: Runnable?, caller: String) {
+ val isEnabled = isEnabled()
+ val key = getKey(entry)
+
if (runnable == null) {
- log { "Runnable is NULL, stop update." }
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, "Runnable NULL, stop")
return
}
- if (!isEnabled()) {
+ if (!isEnabled) {
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key,
+ "NOT ENABLED, run runnable")
runnable.run()
return
}
- log { "\n " }
- val fn = "$label => AvalancheController.update ${getKey(entry)}"
if (entry == null) {
- log { "Entry is NULL, stop update." }
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, "Entry NULL, stop")
return
}
if (debug) {
- debugRunnableLabelMap[runnable] = label
+ debugRunnableLabelMap[runnable] = caller
}
+ var outcome = ""
if (isShowing(entry)) {
- log { "\n$fn => update showing" }
+ outcome = "update showing"
runnable.run()
} else if (entry in nextMap) {
- log { "\n$fn => update next" }
+ outcome = "update next"
nextMap[entry]?.add(runnable)
} else if (headsUpEntryShowing == null) {
- log { "\n$fn => showNow" }
+ outcome = "show now"
showNow(entry, arrayListOf(runnable))
} else {
// Clean up invalid state when entry is in list but not map and vice versa
@@ -156,7 +161,8 @@
)
}
}
- logState("after $fn")
+ outcome += getStateStr()
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, outcome)
}
@VisibleForTesting
@@ -169,32 +175,37 @@
* Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
* all Runnables associated with that entry.
*/
- fun delete(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+ fun delete(entry: HeadsUpEntry?, runnable: Runnable?, caller: String) {
+ val isEnabled = isEnabled()
+ val key = getKey(entry)
+
if (runnable == null) {
- log { "Runnable is NULL, stop delete." }
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key, "Runnable NULL, stop")
return
}
- if (!isEnabled()) {
+ if (!isEnabled) {
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key,
+ "NOT ENABLED, run runnable")
runnable.run()
return
}
- log { "\n " }
- val fn = "$label => AvalancheController.delete " + getKey(entry)
if (entry == null) {
- log { "$fn => entry NULL, running runnable" }
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key,
+ "Entry NULL, run runnable")
runnable.run()
return
}
+ var outcome = ""
if (entry in nextMap) {
- log { "$fn => remove from next" }
+ outcome = "remove from next"
if (entry in nextMap) nextMap.remove(entry)
if (entry in nextList) nextList.remove(entry)
uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_REMOVED)
} else if (entry in debugDropSet) {
- log { "$fn => remove from dropset" }
+ outcome = "remove from dropset"
debugDropSet.remove(entry)
} else if (isShowing(entry)) {
- log { "$fn => remove showing ${getKey(entry)}" }
+ outcome = "remove showing"
previousHunKey = getKey(headsUpEntryShowing)
// Show the next HUN before removing this one, so that we don't tell listeners
// onHeadsUpPinnedModeChanged, which causes
@@ -203,10 +214,10 @@
showNext()
runnable.run()
} else {
- log { "$fn => run runnable for untracked shown ${getKey(entry)}" }
+ outcome = "run runnable for untracked shown"
runnable.run()
}
- logState("after $fn")
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled(), getKey(entry), outcome)
}
/**
@@ -384,23 +395,12 @@
}
private fun getStateStr(): String {
- return "SHOWING: [${getKey(headsUpEntryShowing)}]" +
- "\nPREVIOUS: [$previousHunKey]" +
- "\nNEXT LIST: $nextListStr" +
- "\nNEXT MAP: $nextMapStr" +
- "\nDROPPED: $dropSetStr" +
- "\nENABLED: $enableAtRuntime"
- }
-
- private fun logState(reason: String) {
- log {
- "\n================================================================================="
- }
- log { "STATE $reason" }
- log { getStateStr() }
- log {
- "=================================================================================\n"
- }
+ return "\navalanche state:" +
+ "\n\tshowing: [${getKey(headsUpEntryShowing)}]" +
+ "\n\tprevious: [$previousHunKey]" +
+ "\n\tnext list: $nextListStr" +
+ "\n\tnext map: $nextMapStr" +
+ "\n\tdropped: $dropSetStr"
}
private val dropSetStr: String
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 6ffb162..80c595f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -66,6 +66,30 @@
})
}
+ fun logAvalancheUpdate(caller: String, isEnabled: Boolean, notifEntryKey: String,
+ outcome: String) {
+ buffer.log(TAG, INFO, {
+ str1 = caller
+ str2 = notifEntryKey
+ str3 = outcome
+ bool1 = isEnabled
+ }, {
+ "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3"
+ })
+ }
+
+ fun logAvalancheDelete(caller: String, isEnabled: Boolean, notifEntryKey: String,
+ outcome: String) {
+ buffer.log(TAG, INFO, {
+ str1 = caller
+ str2 = notifEntryKey
+ str3 = outcome
+ bool1 = isEnabled
+ }, {
+ "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3"
+ })
+ }
+
fun logShowNotification(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
str1 = entry.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index 2b094d6..8aa989f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -18,67 +18,155 @@
import android.content.Intent
import android.provider.Settings
+import android.util.Log
import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
import com.android.compose.PlatformButton
import com.android.compose.PlatformOutlinedButton
+import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dialog.ui.composable.AlertDialogContent
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.statusbar.phone.create
import com.android.systemui.statusbar.policy.ui.dialog.composable.ModeTileGrid
import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
+import com.android.systemui.util.Assert
import javax.inject.Inject
+import javax.inject.Provider
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+@SysUISingleton
class ModesDialogDelegate
@Inject
constructor(
private val sysuiDialogFactory: SystemUIDialogFactory,
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val activityStarter: ActivityStarter,
- private val viewModel: ModesDialogViewModel,
+ // Using a provider to avoid a circular dependency.
+ private val viewModel: Provider<ModesDialogViewModel>,
+ @Main private val mainCoroutineContext: CoroutineContext,
) : SystemUIDialog.Delegate {
+ // NOTE: This should only be accessed/written from the main thread.
+ @VisibleForTesting var currentDialog: ComponentSystemUIDialog? = null
+
override fun createDialog(): SystemUIDialog {
- return sysuiDialogFactory.create { dialog ->
- AlertDialogContent(
- title = { Text(stringResource(R.string.zen_modes_dialog_title)) },
- content = { ModeTileGrid(viewModel) },
- neutralButton = {
- PlatformOutlinedButton(
- onClick = {
- val animationController =
- dialogTransitionAnimator.createActivityTransitionController(
- dialog.getButton(SystemUIDialog.BUTTON_NEUTRAL)
- )
- if (animationController == null) {
- // The controller will take care of dismissing for us after the
- // animation, but let's make sure we dismiss the dialog if we don't
- // animate it.
- dialog.dismiss()
- }
- activityStarter.startActivity(
- ZEN_MODE_SETTINGS_INTENT,
- true /* dismissShade */,
- animationController
- )
- }
- ) {
- Text(stringResource(R.string.zen_modes_dialog_settings))
+ Assert.isMainThread()
+ if (currentDialog != null) {
+ Log.w(TAG, "Dialog is already open, dismissing it and creating a new one.")
+ currentDialog?.dismiss()
+ }
+
+ currentDialog = sysuiDialogFactory.create() { ModesDialogContent(it) }
+ currentDialog
+ ?.lifecycle
+ ?.addObserver(
+ object : DefaultLifecycleObserver {
+ override fun onStop(owner: LifecycleOwner) {
+ Assert.isMainThread()
+ currentDialog = null
}
- },
- positiveButton = {
- PlatformButton(onClick = { dialog.dismiss() }) {
- Text(stringResource(R.string.zen_modes_dialog_done))
- }
- },
+ }
+ )
+
+ return currentDialog!!
+ }
+
+ @Composable
+ private fun ModesDialogContent(dialog: SystemUIDialog) {
+ AlertDialogContent(
+ title = { Text(stringResource(R.string.zen_modes_dialog_title)) },
+ content = { ModeTileGrid(viewModel.get()) },
+ neutralButton = {
+ PlatformOutlinedButton(onClick = { openSettings(dialog) }) {
+ Text(stringResource(R.string.zen_modes_dialog_settings))
+ }
+ },
+ positiveButton = {
+ PlatformButton(onClick = { dialog.dismiss() }) {
+ Text(stringResource(R.string.zen_modes_dialog_done))
+ }
+ },
+ )
+ }
+
+ private fun openSettings(dialog: SystemUIDialog) {
+ val animationController =
+ dialogTransitionAnimator.createActivityTransitionController(dialog)
+ if (animationController == null) {
+ // The controller will take care of dismissing for us after
+ // the animation, but let's make sure we dismiss the dialog
+ // if we don't animate it.
+ dialog.dismiss()
+ }
+ activityStarter.startActivity(
+ ZEN_MODE_SETTINGS_INTENT,
+ true /* dismissShade */,
+ animationController
+ )
+ }
+
+ suspend fun showDialog(expandable: Expandable? = null): SystemUIDialog {
+ // Dialogs shown by the DialogTransitionAnimator must be created and shown on the main
+ // thread, so we post it to the UI handler.
+ withContext(mainCoroutineContext) {
+ // Create the dialog if necessary
+ if (currentDialog == null) {
+ createDialog()
+ }
+
+ expandable
+ ?.dialogTransitionController(
+ DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
+ )
+ ?.let { controller -> dialogTransitionAnimator.show(currentDialog!!, controller) }
+ ?: currentDialog!!.show()
+ }
+
+ return currentDialog!!
+ }
+
+ /**
+ * Launches the [intent] by animating from the dialog. If the dialog is not showing, just
+ * launches it normally without animating.
+ */
+ fun launchFromDialog(intent: Intent) {
+ Assert.isMainThread()
+ if (currentDialog == null) {
+ Log.w(
+ TAG,
+ "Cannot launch from dialog, the dialog is not present. " +
+ "Will launch activity without animating."
)
}
+
+ val animationController =
+ currentDialog?.let { dialogTransitionAnimator.createActivityTransitionController(it) }
+ if (animationController == null) {
+ currentDialog?.dismiss()
+ }
+ activityStarter.startActivity(
+ intent,
+ true, /* dismissShade */
+ animationController,
+ )
}
companion object {
+ private const val TAG = "ModesDialogDelegate"
private val ZEN_MODE_SETTINGS_INTENT = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
+ private const val INTERACTION_JANK_TAG = "configure_priority_modes"
}
}
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/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index e84c8b6..c4aa03a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -17,16 +17,21 @@
package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
import android.content.Context
+import android.content.Intent
+import android.provider.Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
+import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID
import com.android.settingslib.notification.modes.ZenMode
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.scan
/**
* Viewmodel for the priority ("zen") modes dialog that can be opened from quick settings. It allows
@@ -39,15 +44,35 @@
val context: Context,
zenModeInteractor: ZenModeInteractor,
@Background val bgDispatcher: CoroutineDispatcher,
+ private val dialogDelegate: ModesDialogDelegate,
) {
// Modes that should be displayed in the dialog
- // TODO(b/346519570): Include modes that have not been set up yet.
private val visibleModes: Flow<List<ZenMode>> =
- zenModeInteractor.modes.map {
- it.filter { mode ->
- mode.rule.isEnabled && (mode.isActive || mode.rule.isManualInvocationAllowed)
+ zenModeInteractor.modes
+ // While this is being collected (or in other words, while the dialog is open), we don't
+ // want a mode to disappear from the list if, for instance, the user deactivates it,
+ // since that can be confusing (similar to how we have visual stability for
+ // notifications while the shade is open).
+ // This ensures new modes are added to the list, and updates to modes already in the
+ // list are registered correctly.
+ .scan(listOf()) { prev, modes ->
+ val prevIds = prev.map { it.id }.toSet()
+
+ modes.filter { mode ->
+ when {
+ // Mode appeared previously -> keep it even if otherwise we may have
+ // filtered it
+ mode.id in prevIds -> true
+ // Mode is enabled -> show if active (so user can toggle off), or if it
+ // can be manually toggled on
+ mode.rule.isEnabled -> mode.isActive || mode.rule.isManualInvocationAllowed
+ // Mode was created as disabled, or disabled by the app that owns it ->
+ // will be shown with a "Set up" text
+ !mode.rule.isEnabled -> mode.status == ZenMode.Status.DISABLED_BY_OTHER
+ else -> false
+ }
+ }
}
- }
val tiles: Flow<List<ModeTileViewModel>> =
visibleModes
@@ -63,23 +88,39 @@
// "ON: Do Not Disturb, Until Mon 08:09"; see DndTile.
contentDescription = "",
onClick = {
- if (mode.isActive) {
+ if (!mode.rule.isEnabled) {
+ openSettings(mode)
+ } else if (mode.isActive) {
zenModeInteractor.deactivateMode(mode)
} else {
- // TODO(b/346519570): Handle duration for DND mode.
- zenModeInteractor.activateMode(mode)
+ if (mode.rule.isManualInvocationAllowed) {
+ // TODO(b/346519570): Handle duration for DND mode.
+ zenModeInteractor.activateMode(mode)
+ }
}
},
- onLongClick = {
- // TODO(b/346519570): Open settings page for mode.
- }
+ onLongClick = { openSettings(mode) }
)
}
}
.flowOn(bgDispatcher)
+ private fun openSettings(mode: ZenMode) {
+ val intent: Intent =
+ Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
+ .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, mode.id)
+
+ dialogDelegate.launchFromDialog(intent)
+ }
+
private fun getTileSubtext(mode: ZenMode): String {
- // TODO(b/346519570): Use ZenModeConfig.getDescription for manual DND
+ if (!mode.rule.isEnabled) {
+ return context.resources.getString(R.string.zen_mode_set_up)
+ }
+ if (!mode.rule.isManualInvocationAllowed && !mode.isActive) {
+ return context.resources.getString(R.string.zen_mode_no_manual_invocation)
+ }
+
val on = context.resources.getString(R.string.zen_mode_on)
val off = context.resources.getString(R.string.zen_mode_off)
return mode.rule.triggerDescription ?: if (mode.isActive) on else off
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
index 4869114..89227cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
@@ -24,6 +24,7 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import javax.inject.Inject
@@ -33,6 +34,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
/**
@@ -54,12 +56,20 @@
keyguardStatusBarInteractor: KeyguardStatusBarInteractor,
batteryController: BatteryController,
) {
+
+ private val showingHeadsUpStatusBar: Flow<Boolean> =
+ if (NotificationsHeadsUpRefactor.isEnabled) {
+ headsUpNotificationInteractor.showHeadsUpStatusBar
+ } else {
+ flowOf(false)
+ }
+
/** True if this view should be visible and false otherwise. */
val isVisible: StateFlow<Boolean> =
combine(
sceneInteractor.currentScene,
keyguardInteractor.isDozing,
- headsUpNotificationInteractor.showHeadsUpStatusBar,
+ showingHeadsUpStatusBar,
) { currentScene, isDozing, showHeadsUpStatusBar ->
currentScene == Scenes.Lockscreen && !isDozing && !showHeadsUpStatusBar
}
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/src/com/android/systemui/touchpad/tutorial/TouchpadKeyboardTutorialModule.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadKeyboardTutorialModule.kt
new file mode 100644
index 0000000..8ba8db4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadKeyboardTutorialModule.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.touchpad.tutorial
+
+import android.app.Activity
+import com.android.systemui.touchpad.tutorial.ui.view.TouchpadTutorialActivity
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface TouchpadKeyboardTutorialModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(TouchpadTutorialActivity::class)
+ fun activity(impl: TouchpadTutorialActivity): Activity
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index 8721b69..94ff65e 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -21,7 +21,9 @@
import android.graphics.PorterDuffColorFilter
import androidx.activity.compose.BackHandler
import androidx.annotation.StringRes
-import androidx.compose.foundation.background
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -40,6 +42,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.pointerInteropFilter
@@ -63,6 +66,7 @@
data class TutorialScreenColors(
val backgroundColor: Color,
+ val successBackgroundColor: Color,
val titleColor: Color,
val animationProperties: LottieDynamicProperties
)
@@ -100,6 +104,7 @@
val onTertiaryFixed = LocalAndroidColorScheme.current.onTertiaryFixed
val onTertiaryFixedVariant = LocalAndroidColorScheme.current.onTertiaryFixedVariant
val tertiaryFixedDim = LocalAndroidColorScheme.current.tertiaryFixedDim
+ val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer
val dynamicProperties =
rememberLottieDynamicProperties(
rememberColorFilterProperty(".tertiaryFixedDim", tertiaryFixedDim),
@@ -108,9 +113,10 @@
rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant)
)
val screenColors =
- remember(onTertiaryFixed, tertiaryFixedDim, dynamicProperties) {
+ remember(onTertiaryFixed, surfaceContainer, tertiaryFixedDim, dynamicProperties) {
TutorialScreenColors(
backgroundColor = onTertiaryFixed,
+ successBackgroundColor = surfaceContainer,
titleColor = tertiaryFixedDim,
animationProperties = dynamicProperties,
)
@@ -124,11 +130,19 @@
onDoneButtonClicked: () -> Unit,
screenColors: TutorialScreenColors
) {
+ val animatedColor by
+ animateColorAsState(
+ targetValue =
+ if (gestureDone) screenColors.successBackgroundColor
+ else screenColors.backgroundColor,
+ animationSpec = tween(durationMillis = 150, easing = LinearEasing),
+ label = "backgroundColor"
+ )
Column(
verticalArrangement = Arrangement.Center,
modifier =
Modifier.fillMaxSize()
- .background(color = screenColors.backgroundColor)
+ .drawBehind { drawRect(animatedColor) }
.padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp)
) {
Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
@@ -142,6 +156,7 @@
)
Spacer(modifier = Modifier.width(76.dp))
TutorialAnimation(
+ gestureDone,
screenColors.animationProperties,
modifier = Modifier.weight(1f).padding(top = 8.dp)
)
@@ -173,14 +188,18 @@
}
@Composable
-fun TutorialAnimation(animationProperties: LottieDynamicProperties, modifier: Modifier = Modifier) {
+fun TutorialAnimation(
+ gestureDone: Boolean,
+ animationProperties: LottieDynamicProperties,
+ modifier: Modifier = Modifier
+) {
Column(modifier = modifier.fillMaxWidth()) {
- val composition by
- rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.trackpad_back_edu))
+ val resId = if (gestureDone) R.raw.trackpad_back_success else R.raw.trackpad_back_edu
+ val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(resId))
val progress by
animateLottieCompositionAsState(
composition,
- iterations = LottieConstants.IterateForever
+ iterations = if (gestureDone) 1 else LottieConstants.IterateForever
)
LottieAnimation(
composition = composition,
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java
index 8ab5bc6..169f865 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java
@@ -26,8 +26,8 @@
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
@@ -64,7 +64,7 @@
ThresholdSensor mSecondaryThresholdSensor;
private final DelayableExecutor mDelayableExecutor;
private final Execution mExecution;
- private final List<ThresholdSensor.Listener> mListeners = new ArrayList<>();
+ private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
private String mTag = null;
@VisibleForTesting protected boolean mPaused;
private ThresholdSensorEvent mLastPrimaryEvent;
@@ -246,7 +246,7 @@
public void unregister(ThresholdSensor.Listener listener) {
mExecution.assertIsMainThread();
mListeners.remove(listener);
- if (mListeners.size() == 0) {
+ if (mListeners.isEmpty()) {
unregisterInternal();
}
}
@@ -296,8 +296,7 @@
}
if (mLastEvent != null) {
ThresholdSensorEvent lastEvent = mLastEvent; // Listeners can null out mLastEvent.
- List<ThresholdSensor.Listener> listeners = new ArrayList<>(mListeners);
- listeners.forEach(proximitySensorListener ->
+ mListeners.forEach(proximitySensorListener ->
proximitySensorListener.onThresholdCrossed(lastEvent));
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningAction.kt b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningAction.kt
new file mode 100644
index 0000000..a77acb5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningAction.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.volume
+
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
+import android.app.PendingIntent.FLAG_UPDATE_CURRENT
+import android.content.Context
+import android.content.Intent
+
+/**
+ * label: Notification action label text. intent: The Intent used to start Activity or Broadcast.
+ * isActivity: Defines if the pending intent should start an activity. Default is to broadcast
+ */
+data class CsdWarningAction(
+ val label: String? = null,
+ val intent: Intent? = null,
+ val isActivity: Boolean = false,
+) {
+ fun toPendingIntent(context: Context): PendingIntent? {
+ if (label == null || intent == null) {
+ return null
+ }
+ if (isActivity) {
+ return PendingIntent.getActivity(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ }
+ return PendingIntent.getBroadcast(context, 0, intent, FLAG_IMMUTABLE)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
index bb230e6..a63660b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
@@ -30,7 +30,6 @@
import android.media.AudioManager;
import android.provider.Settings;
import android.util.Log;
-import android.util.Pair;
import android.view.KeyEvent;
import android.view.WindowManager;
@@ -109,7 +108,7 @@
private long mShowTime;
@VisibleForTesting public int mCachedMediaStreamVolume;
- private Optional<ImmutableList<Pair<String, Intent>>> mActionIntents;
+ private Optional<ImmutableList<CsdWarningAction>> mActionIntents;
private final BroadcastDispatcher mBroadcastDispatcher;
/**
@@ -121,7 +120,7 @@
CsdWarningDialog create(
int csdWarning,
Runnable onCleanup,
- Optional<ImmutableList<Pair<String, Intent>>> actionIntents);
+ Optional<ImmutableList<CsdWarningAction>> actionIntents);
}
@AssistedInject
@@ -132,7 +131,7 @@
NotificationManager notificationManager,
@Background DelayableExecutor delayableExecutor,
@Assisted Runnable onCleanup,
- @Assisted Optional<ImmutableList<Pair<String, Intent>>> actionIntents,
+ @Assisted Optional<ImmutableList<CsdWarningAction>> actionIntents,
BroadcastDispatcher broadcastDispatcher) {
super(context);
mCsdWarning = csdWarning;
@@ -351,39 +350,45 @@
if (Flags.sounddoseCustomization()
&& mActionIntents.isPresent()
&& !mActionIntents.get().isEmpty()) {
- ImmutableList<Pair<String, Intent>> actionIntentsList = mActionIntents.get();
- for (Pair<String, Intent> intentPair : actionIntentsList) {
- if (intentPair != null && intentPair.first != null && intentPair.second != null) {
- PendingIntent pendingActionIntent =
- PendingIntent.getBroadcast(mContext, 0, intentPair.second,
- FLAG_IMMUTABLE);
- builder.addAction(0, intentPair.first, pendingActionIntent);
- // Register receiver to undo volume only when
- // notification conaining the undo action would be sent.
- if (intentPair.first == mContext.getString(R.string.volume_undo_action)) {
- final IntentFilter filterUndo = new IntentFilter(
- VolumeDialog.ACTION_VOLUME_UNDO);
- mBroadcastDispatcher.registerReceiver(mReceiverUndo,
- filterUndo,
- /* executor = default */ null,
- /* user = default */ null,
- Context.RECEIVER_NOT_EXPORTED,
- /* permission = default */ null);
+ ImmutableList<CsdWarningAction> actionIntentsList = mActionIntents.get();
+ for (CsdWarningAction action : actionIntentsList) {
+ if (action.getLabel() == null || action.getIntent() == null) {
+ Log.w(TAG, "Null action intent received. Skipping addition to notification");
+ continue;
+ }
+ PendingIntent pendingActionIntent = action.toPendingIntent(mContext);
+ if (pendingActionIntent == null) {
+ Log.w(TAG, "Null pending intent received. Skipping addition to notification");
+ continue;
+ }
+ builder.addAction(0, action.getLabel(), pendingActionIntent);
- // Register receiver to learn if notification has been dismissed.
- // This is required to unregister receivers to prevent leak.
- Intent dismissIntent = new Intent(DISMISS_CSD_NOTIFICATION)
- .setPackage(mContext.getPackageName());
- PendingIntent pendingDismissIntent = PendingIntent.getBroadcast(mContext,
- 0, dismissIntent, FLAG_IMMUTABLE);
- mBroadcastDispatcher.registerReceiver(mReceiverDismissNotification,
- new IntentFilter(DISMISS_CSD_NOTIFICATION),
- /* executor = default */ null,
- /* user = default */ null,
- Context.RECEIVER_NOT_EXPORTED,
- /* permission = default */ null);
- builder.setDeleteIntent(pendingDismissIntent);
- }
+ // Register receiver to undo volume only when
+ // notification conaining the undo action would be sent.
+ if (action.getLabel().equals(mContext.getString(R.string.volume_undo_action))) {
+ final IntentFilter filterUndo = new IntentFilter(
+ VolumeDialog.ACTION_VOLUME_UNDO);
+ mBroadcastDispatcher.registerReceiver(mReceiverUndo,
+ filterUndo,
+ /* executor = default */ null,
+ /* user = default */ null,
+ Context.RECEIVER_NOT_EXPORTED,
+ /* permission = default */ null);
+
+ // Register receiver to learn if notification has been dismissed.
+ // This is required to unregister receivers to prevent leak.
+ Intent dismissIntent = new Intent(DISMISS_CSD_NOTIFICATION)
+ .setPackage(mContext.getPackageName());
+ PendingIntent pendingDismissIntent = PendingIntent.getBroadcast(
+ mContext,
+ 0, dismissIntent, FLAG_IMMUTABLE);
+ mBroadcastDispatcher.registerReceiver(mReceiverDismissNotification,
+ new IntentFilter(DISMISS_CSD_NOTIFICATION),
+ /* executor = default */ null,
+ /* user = default */ null,
+ Context.RECEIVER_NOT_EXPORTED,
+ /* permission = default */ null);
+ builder.setDeleteIntent(pendingDismissIntent);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 0770d89..e56f6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -51,7 +51,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -79,7 +78,6 @@
import android.provider.Settings.Global;
import android.text.InputFilter;
import android.util.Log;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.view.ContextThemeWrapper;
@@ -322,8 +320,8 @@
private final VolumePanelFlag mVolumePanelFlag;
private final VolumeDialogInteractor mInteractor;
// Optional actions for soundDose
- private Optional<ImmutableList<Pair<String, Intent>>> mCsdWarningNotificationActions =
- Optional.of(ImmutableList.of());
+ private Optional<ImmutableList<CsdWarningAction>>
+ mCsdWarningNotificationActions = Optional.of(ImmutableList.of());
public VolumeDialogImpl(
Context context,
@@ -2231,7 +2229,7 @@
}
public void setCsdWarningNotificationActionIntents(
- ImmutableList<Pair<String, Intent>> actionIntent) {
+ ImmutableList<CsdWarningAction> actionIntent) {
mCsdWarningNotificationActions = Optional.of(actionIntent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index eb2f71a1..0c1bc21 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -19,11 +19,13 @@
import android.content.ContentResolver
import android.content.Context
import android.media.AudioManager
+import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
import com.android.settingslib.volume.data.repository.AudioSharingRepository
+import com.android.settingslib.volume.data.repository.AudioSharingRepositoryEmptyImpl
import com.android.settingslib.volume.data.repository.AudioSharingRepositoryImpl
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
@@ -79,13 +81,16 @@
@Application coroutineScope: CoroutineScope,
@Background coroutineContext: CoroutineContext,
): AudioSharingRepository =
- AudioSharingRepositoryImpl(
- context,
- contentResolver,
- localBluetoothManager,
- coroutineScope,
- coroutineContext
- )
+ if (BluetoothUtils.isAudioSharingEnabled() && localBluetoothManager != null) {
+ AudioSharingRepositoryImpl(
+ contentResolver,
+ localBluetoothManager,
+ coroutineScope,
+ coroutineContext
+ )
+ } else {
+ AudioSharingRepositoryEmptyImpl()
+ }
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt
index 9f1e60e..1c80887 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt
@@ -16,14 +16,14 @@
package com.android.systemui.volume.dagger
-import com.android.settingslib.volume.data.repository.AudioSharingRepository
+import com.android.settingslib.flags.Flags
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractorEmptyImpl
import com.android.systemui.volume.domain.interactor.AudioSharingInteractorImpl
+import dagger.Lazy
import dagger.Module
import dagger.Provides
-import kotlinx.coroutines.CoroutineScope
/** Dagger module for audio sharing code in the volume package */
@Module
@@ -33,8 +33,13 @@
@Provides
@SysUISingleton
fun provideAudioSharingInteractor(
- @Application coroutineScope: CoroutineScope,
- repository: AudioSharingRepository
- ): AudioSharingInteractor = AudioSharingInteractorImpl(coroutineScope, repository)
+ impl: Lazy<AudioSharingInteractorImpl>,
+ emptyImpl: Lazy<AudioSharingInteractorEmptyImpl>,
+ ): AudioSharingInteractor =
+ if (Flags.volumeDialogAudioSharingFix()) {
+ impl.get()
+ } else {
+ emptyImpl.get()
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 6dcea14..9df653f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -74,7 +74,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -92,6 +91,7 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -1379,6 +1379,28 @@
}
@Test
+ @EnableFlags(FLAG_BP_TALKBACK)
+ fun no_hint_for_talkback_guidance_after_auth() = runGenericTest {
+ val hint by collectLastValue(kosmos.promptViewModel.accessibilityHint)
+
+ kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 0)
+ kosmos.promptViewModel.confirmAuthenticated()
+
+ // Touches should fall outside of sensor area
+ whenever(kosmos.udfpsUtils.getTouchInNativeCoordinates(any(), any(), any()))
+ .thenReturn(Point(0, 0))
+ whenever(kosmos.udfpsUtils.onTouchOutsideOfSensorArea(any(), any(), any(), any(), any()))
+ .thenReturn("Direction")
+
+ kosmos.promptViewModel.onAnnounceAccessibilityHint(
+ obtainMotionEvent(MotionEvent.ACTION_HOVER_ENTER),
+ true
+ )
+
+ assertThat(hint.isNullOrBlank()).isTrue()
+ }
+
+ @Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun descriptionOverriddenByVerticalListContentView() =
runGenericTest(description = "test description", contentView = promptContentView) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
index 63b4ff7..72e0726 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
@@ -18,6 +18,7 @@
import android.content.res.Configuration
import android.graphics.Rect
+import android.util.LayoutDirection
import android.view.Surface
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -34,6 +35,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@ExperimentalCoroutinesApi
@SmallTest
@@ -70,6 +73,28 @@
}
@Test
+ fun directionalDimensionPixelSize() =
+ testScope.runTest {
+ val resourceId = 1001
+ val pixelSize = 501
+ configurationRepository.setDimensionPixelSize(resourceId, pixelSize)
+
+ val config: Configuration = mock()
+ val dimensionPixelSize by
+ collectLastValue(
+ underTest.directionalDimensionPixelSize(LayoutDirection.LTR, resourceId)
+ )
+
+ whenever(config.layoutDirection).thenReturn(LayoutDirection.LTR)
+ configurationRepository.onConfigurationChange(config)
+ assertThat(dimensionPixelSize).isEqualTo(pixelSize)
+
+ whenever(config.layoutDirection).thenReturn(LayoutDirection.RTL)
+ configurationRepository.onConfigurationChange(config)
+ assertThat(dimensionPixelSize).isEqualTo(-pixelSize)
+ }
+
+ @Test
fun dimensionPixelSizes() =
testScope.runTest {
val resourceId1 = 1001
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
similarity index 82%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index 7936ccc..c2c94a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -23,6 +23,9 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.LayoutDirection;
import android.view.GestureDetector;
import android.view.MotionEvent;
@@ -68,10 +71,12 @@
AtomicReference reference = new AtomicReference<>(null);
when(mLifecycle.getInternalScopeRef()).thenReturn(reference);
when(mLifecycle.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
mTouchHandler = new CommunalTouchHandler(
Optional.of(mCentralSurfaces),
INITIATION_WIDTH,
mKosmos.getCommunalInteractor(),
+ mKosmos.getConfigurationInteractor(),
mLifecycle
);
}
@@ -127,4 +132,26 @@
.onScroll(motionEvent1, motionEvent2, 1, 1))
.isTrue();
}
+
+ @Test
+ public void testTouchInitiationArea() {
+ final int right = 80;
+ final int bottom = 100;
+ final Rect bounds = new Rect(0, 0, right, bottom);
+
+ {
+ final Region region = new Region();
+ mTouchHandler.mLayoutDirectionCallback.accept(LayoutDirection.LTR);
+ mTouchHandler.getTouchInitiationRegion(bounds, region, null);
+ assertThat(region.getBounds()).isEqualTo(
+ new Rect(right - INITIATION_WIDTH, 0, right, bottom));
+ }
+
+ {
+ final Region region = new Region();
+ mTouchHandler.mLayoutDirectionCallback.accept(LayoutDirection.RTL);
+ mTouchHandler.getTouchInitiationRegion(bounds, region, null);
+ assertThat(region.getBounds()).isEqualTo(new Rect(0, 0, INITIATION_WIDTH, bottom));
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index a1fe0f0..3388a78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -27,6 +27,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD
import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
@@ -67,6 +68,7 @@
@Mock private lateinit var systemProperties: SystemPropertiesHelper
@Mock private lateinit var resources: Resources
@Mock private lateinit var restarter: Restarter
+ private lateinit var userTracker: FakeUserTracker
private val flagMap = mutableMapOf<String, Flag<*>>()
private lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var clearCacheAction: Consumer<String>
@@ -78,9 +80,11 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
-
flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
flagMap.put(releasedFlagB.name, releasedFlagB)
+
+ userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockContext })
+
mFeatureFlagsClassicDebug =
FeatureFlagsClassicDebug(
flagManager,
@@ -90,7 +94,8 @@
resources,
serverFlagReader,
flagMap,
- restarter
+ restarter,
+ userTracker
)
mFeatureFlagsClassicDebug.init()
verify(flagManager).onSettingsChangedAction = any()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index 0043173..6b60740 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyboard.shortcut.ui.viewmodel
+import android.app.role.RoleManager
+import android.app.role.mockRoleManager
import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
@@ -45,7 +47,9 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
+import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -77,12 +81,15 @@
private val testScope = kosmos.testScope
private val testHelper = kosmos.shortcutHelperTestHelper
private val sysUiState = kosmos.sysUiState
+ private val fakeUserTracker = kosmos.fakeUserTracker
+ private val mockRoleManager = kosmos.mockRoleManager
private val viewModel = kosmos.shortcutHelperViewModel
@Before
fun setUp() {
fakeSystemSource.setGroups(TestShortcuts.systemGroups)
fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
+ fakeCurrentAppsSource.setGroups(TestShortcuts.currentAppGroups)
}
@Test
@@ -216,21 +223,21 @@
}
@Test
- fun shortcutsUiState_featureActive_emitsActiveWithFirstCategorySelectedByDefault() =
+ fun shortcutsUiState_noCurrentAppCategory_defaultSelectedCategoryIsSystem() =
testScope.runTest {
+ fakeCurrentAppsSource.setGroups(emptyList())
+
val uiState by collectLastValue(viewModel.shortcutsUiState)
testHelper.showFromActivity()
val activeUiState = uiState as ShortcutsUiState.Active
- assertThat(activeUiState.defaultSelectedCategory)
- .isEqualTo(activeUiState.shortcutCategories.first().type)
+ assertThat(activeUiState.defaultSelectedCategory).isEqualTo(System)
}
@Test
- fun shortcutsUiState_featureActive_emitsActiveWithCurrentAppsCategorySelectedWhenPresent() =
+ fun shortcutsUiState_currentAppCategoryPresent_currentAppIsDefaultSelected() =
testScope.runTest {
- fakeCurrentAppsSource.setGroups(TestShortcuts.currentAppGroups)
val uiState by collectLastValue(viewModel.shortcutsUiState)
testHelper.showFromActivity()
@@ -241,6 +248,24 @@
}
@Test
+ fun shortcutsUiState_currentAppIsLauncher_defaultSelectedCategoryIsSystem() =
+ testScope.runTest {
+ whenever(
+ mockRoleManager.getRoleHoldersAsUser(
+ RoleManager.ROLE_HOME,
+ fakeUserTracker.userHandle
+ )
+ )
+ .thenReturn(listOf(TestShortcuts.currentAppPackageName))
+ val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+ testHelper.showFromActivity()
+
+ val activeUiState = uiState as ShortcutsUiState.Active
+ assertThat(activeUiState.defaultSelectedCategory).isEqualTo(System)
+ }
+
+ @Test
fun shortcutsUiState_userTypedQuery_filtersMatchingShortcutLabels() =
testScope.runTest {
fakeSystemSource.setGroups(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 506c5ae..29cd9a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -30,6 +30,7 @@
import android.view.SurfaceControlViewHost
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.SystemUIAppComponentFactoryBase
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
@@ -96,7 +97,8 @@
@Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
- @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
+ @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
private lateinit var dockManager: DockManagerFake
private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
@@ -199,6 +201,7 @@
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
logger = logger,
+ metricsLogger = metricsLogger,
devicePolicyManager = devicePolicyManager,
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index f726aae..e251ab5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argThat
import com.android.systemui.util.mockito.whenever
+import java.util.function.Predicate
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -46,7 +47,6 @@
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.clearInvocations
-import java.util.function.Predicate
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -54,70 +54,134 @@
class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
- @Mock
- private lateinit var windowManager: WindowManager
- @Mock
- private lateinit var keyguardViewMediator: KeyguardViewMediator
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var keyguardViewController: KeyguardViewController
- @Mock
- private lateinit var featureFlags: FeatureFlags
- @Mock
- private lateinit var biometricUnlockController: BiometricUnlockController
- @Mock
- private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
- @Mock
- private lateinit var statusBarStateController: SysuiStatusBarStateController
- @Mock
- private lateinit var notificationShadeWindowController: NotificationShadeWindowController
- @Mock
- private lateinit var powerManager: PowerManager
- @Mock
- private lateinit var wallpaperManager: WallpaperManager
+ @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+ @Mock private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock private lateinit var powerManager: PowerManager
+ @Mock private lateinit var wallpaperManager: WallpaperManager
@Mock
private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController.Stub
private var surfaceControl1 = mock(SurfaceControl::class.java)
- private var remoteTarget1 = RemoteAnimationTarget(
- 0 /* taskId */, 0, surfaceControl1, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
- mock(WindowConfiguration::class.java), false, surfaceControl1, Rect(),
- mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var remoteTarget1 =
+ RemoteAnimationTarget(
+ 0 /* taskId */,
+ 0,
+ surfaceControl1,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControl1,
+ Rect(),
+ mock(ActivityManager.RunningTaskInfo::class.java),
+ false
+ )
private var surfaceControl2 = mock(SurfaceControl::class.java)
- private var remoteTarget2 = RemoteAnimationTarget(
- 1 /* taskId */, 0, surfaceControl2, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
- mock(WindowConfiguration::class.java), false, surfaceControl2, Rect(),
- mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var remoteTarget2 =
+ RemoteAnimationTarget(
+ 1 /* taskId */,
+ 0,
+ surfaceControl2,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControl2,
+ Rect(),
+ mock(ActivityManager.RunningTaskInfo::class.java),
+ false
+ )
private lateinit var remoteAnimationTargets: Array<RemoteAnimationTarget>
private var surfaceControlWp = mock(SurfaceControl::class.java)
- private var wallpaperTarget = RemoteAnimationTarget(
- 2 /* taskId */, 0, surfaceControlWp, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
- mock(WindowConfiguration::class.java), false, surfaceControlWp, Rect(),
- mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var wallpaperTarget =
+ RemoteAnimationTarget(
+ 2 /* taskId */,
+ 0,
+ surfaceControlWp,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControlWp,
+ Rect(),
+ mock(ActivityManager.RunningTaskInfo::class.java),
+ false
+ )
private lateinit var wallpaperTargets: Array<RemoteAnimationTarget>
private var surfaceControlLockWp = mock(SurfaceControl::class.java)
- private var lockWallpaperTarget = RemoteAnimationTarget(
- 3 /* taskId */, 0, surfaceControlLockWp, false, Rect(), Rect(), 0, Point(), Rect(),
- Rect(), mock(WindowConfiguration::class.java), false, surfaceControlLockWp,
- Rect(), mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var lockWallpaperTarget =
+ RemoteAnimationTarget(
+ 3 /* taskId */,
+ 0,
+ surfaceControlLockWp,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControlLockWp,
+ Rect(),
+ mock(ActivityManager.RunningTaskInfo::class.java),
+ false
+ )
private lateinit var lockWallpaperTargets: Array<RemoteAnimationTarget>
+ private var shouldPerformSmartspaceTransition = false
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
- windowManager, context.resources,
- keyguardStateController, { keyguardViewMediator }, keyguardViewController,
- featureFlags, { biometricUnlockController }, statusBarStateController,
- notificationShadeWindowController, powerManager, wallpaperManager
- )
+ keyguardUnlockAnimationController =
+ object :
+ KeyguardUnlockAnimationController(
+ windowManager,
+ context.resources,
+ keyguardStateController,
+ { keyguardViewMediator },
+ keyguardViewController,
+ featureFlags,
+ { biometricUnlockController },
+ statusBarStateController,
+ notificationShadeWindowController,
+ powerManager,
+ wallpaperManager
+ ) {
+ override fun shouldPerformSmartspaceTransition(): Boolean =
+ shouldPerformSmartspaceTransition
+ }
keyguardUnlockAnimationController.setLauncherUnlockController(
- "", launcherUnlockAnimationController)
+ "",
+ launcherUnlockAnimationController
+ )
whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
whenever(powerManager.isInteractive).thenReturn(true)
@@ -159,8 +223,8 @@
)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1)).scheduleApply(
- captorSb.capture { sp -> sp.surface == surfaceControl1 })
+ verify(surfaceTransactionApplier, times(1))
+ .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
val params = captorSb.getLastValue()
@@ -171,15 +235,13 @@
// Also expect we've immediately asked the keyguard view mediator to finish the remote
// animation.
- verify(keyguardViewMediator, times(1)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ verify(keyguardViewMediator, times(1))
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
verifyNoMoreInteractions(surfaceTransactionApplier)
}
- /**
- * If we are not wake and unlocking, we expect the unlock animation to play normally.
- */
+ /** If we are not wake and unlocking, we expect the unlock animation to play normally. */
@Test
fun surfaceAnimation_ifNotWakeAndUnlocking() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(false)
@@ -193,18 +255,18 @@
)
// Since the animation is running, we should not have finished the remote animation.
- verify(keyguardViewMediator, times(0)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ verify(keyguardViewMediator, times(0))
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
@Test
fun onWakeAndUnlock_notifiesListenerWithTrue() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
- whenever(biometricUnlockController.mode).thenReturn(
- BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+ whenever(biometricUnlockController.mode)
+ .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
- val listener = mock(
- KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
+ val listener =
+ mock(KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
@@ -221,11 +283,11 @@
@Test
fun onWakeAndUnlockFromDream_notifiesListenerWithFalse() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
- whenever(biometricUnlockController.mode).thenReturn(
- BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+ whenever(biometricUnlockController.mode)
+ .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
- val listener = mock(
- KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
+ val listener =
+ mock(KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
@@ -269,8 +331,8 @@
* keyguard. This means this was a swipe to dismiss gesture but the user flung the keyguard and
* lifted their finger while we were requesting the surface be made visible.
*
- * In this case, we should verify that we are playing the canned unlock animation and not
- * simply fading in the surface.
+ * In this case, we should verify that we are playing the canned unlock animation and not simply
+ * fading in the surface.
*/
@Test
fun playCannedUnlockAnimation_ifRequestedShowSurface_andFlinging() {
@@ -293,8 +355,8 @@
* ever happened and we're just playing the simple canned animation (happens via UDFPS unlock,
* long press on the lock icon, etc).
*
- * In this case, we should verify that we are playing the canned unlock animation and not
- * simply fading in the surface.
+ * In this case, we should verify that we are playing the canned unlock animation and not simply
+ * fading in the surface.
*/
@Test
fun playCannedUnlockAnimation_ifDidNotRequestShowSurface() {
@@ -332,11 +394,11 @@
keyguardUnlockAnimationController.willUnlockWithInWindowLauncherAnimations = true
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTargets,
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ remoteAnimationTargets,
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation())
@@ -353,11 +415,11 @@
var lastFadeOutAlpha = -1f
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- arrayOf(remoteTarget1, remoteTarget2),
- wallpaperTargets,
- lockWallpaperTargets,
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ arrayOf(remoteTarget1, remoteTarget2),
+ wallpaperTargets,
+ lockWallpaperTargets,
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
for (i in 0..10) {
@@ -367,19 +429,22 @@
keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(amount)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(2)).scheduleApply(
+ verify(surfaceTransactionApplier, times(2))
+ .scheduleApply(
captorSb.capture { sp ->
- sp.surface == surfaceControlWp || sp.surface == surfaceControlLockWp })
+ sp.surface == surfaceControlWp || sp.surface == surfaceControlLockWp
+ }
+ )
val fadeInAlpha = captorSb.getLastValue { it.surface == surfaceControlWp }.alpha
val fadeOutAlpha = captorSb.getLastValue { it.surface == surfaceControlLockWp }.alpha
if (amount == 0f) {
- assertTrue (fadeInAlpha == 0f)
- assertTrue (fadeOutAlpha == 1f)
+ assertTrue(fadeInAlpha == 0f)
+ assertTrue(fadeOutAlpha == 1f)
} else if (amount == 1f) {
- assertTrue (fadeInAlpha == 1f)
- assertTrue (fadeOutAlpha == 0f)
+ assertTrue(fadeInAlpha == 1f)
+ assertTrue(fadeOutAlpha == 0f)
} else {
assertTrue(fadeInAlpha >= lastFadeInAlpha)
assertTrue(fadeOutAlpha <= lastFadeOutAlpha)
@@ -389,18 +454,16 @@
}
}
- /**
- * If we are not wake and unlocking, we expect the unlock animation to play normally.
- */
+ /** If we are not wake and unlocking, we expect the unlock animation to play normally. */
@Test
@DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun surfaceAnimation_multipleTargets() {
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- arrayOf(remoteTarget1, remoteTarget2),
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ arrayOf(remoteTarget1, remoteTarget2),
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
// Cancel the animator so we can verify only the setSurfaceBehind call below.
@@ -412,12 +475,18 @@
keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(0.5f)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(2)).scheduleApply(captorSb
- .capture { sp -> sp.surface == surfaceControl1 || sp.surface == surfaceControl2 })
+ verify(surfaceTransactionApplier, times(2))
+ .scheduleApply(
+ captorSb.capture { sp ->
+ sp.surface == surfaceControl1 || sp.surface == surfaceControl2
+ }
+ )
val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1).description(
- "WallpaperSurface was expected to receive scheduleApply once"
- )).scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp})
+ verify(
+ surfaceTransactionApplier,
+ times(1).description("WallpaperSurface was expected to receive scheduleApply once")
+ )
+ .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
val allParams = captorSb.getAllValues()
@@ -432,8 +501,8 @@
assertTrue(remainingTargets.isEmpty())
// Since the animation is running, we should not have finished the remote animation.
- verify(keyguardViewMediator, times(0)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ verify(keyguardViewMediator, times(0))
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
@Test
@@ -442,11 +511,11 @@
whenever(powerManager.isInteractive).thenReturn(false)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTargets,
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ remoteAnimationTargets,
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
// Cancel the animator so we can verify only the setSurfaceBehind call below.
@@ -457,12 +526,14 @@
keyguardUnlockAnimationController.setWallpaperAppearAmount(1f, wallpaperTargets)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1)).scheduleApply(
- captorSb.capture { sp -> sp.surface == surfaceControl1})
+ verify(surfaceTransactionApplier, times(1))
+ .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, atLeastOnce().description("Wallpaper surface has not " +
- "received scheduleApply")).scheduleApply(
- captorWp.capture { sp -> sp.surface == surfaceControlWp })
+ verify(
+ surfaceTransactionApplier,
+ atLeastOnce().description("Wallpaper surface has not " + "received scheduleApply")
+ )
+ .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
val params = captorSb.getLastValue()
@@ -479,11 +550,11 @@
whenever(powerManager.isInteractive).thenReturn(true)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTargets,
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ remoteAnimationTargets,
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
// Stop the animator - we just want to test whether the override is not applied.
@@ -494,24 +565,31 @@
keyguardUnlockAnimationController.setWallpaperAppearAmount(1f, wallpaperTargets)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1)).scheduleApply(
- captorSb.capture { sp -> sp.surface == surfaceControl1 })
+ verify(surfaceTransactionApplier, times(1))
+ .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, atLeastOnce().description("Wallpaper surface has not " +
- "received scheduleApply")).scheduleApply(
- captorWp.capture { sp -> sp.surface == surfaceControlWp })
+ verify(
+ surfaceTransactionApplier,
+ atLeastOnce().description("Wallpaper surface has not " + "received scheduleApply")
+ )
+ .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
val params = captorSb.getLastValue()
assertEquals(1f, params.alpha)
assertTrue(params.matrix.isIdentity)
- assertEquals("Wallpaper surface was expected to have opacity 1",
- 1f, captorWp.getLastValue().alpha)
+ assertEquals(
+ "Wallpaper surface was expected to have opacity 1",
+ 1f,
+ captorWp.getLastValue().alpha
+ )
verifyNoMoreInteractions(surfaceTransactionApplier)
}
@Test
- fun unlockToLauncherWithInWindowAnimations_ssViewIsVisible() {
+ fun unlockToLauncherWithInWindowAnimations_ssViewInVisible_whenPerformSSTransition() {
+ shouldPerformSmartspaceTransition = true
+
val mockLockscreenSmartspaceView = mock(View::class.java)
whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.VISIBLE)
keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
@@ -522,6 +600,19 @@
}
@Test
+ fun unlockToLauncherWithInWindowAnimations_ssViewVisible_whenNotPerformSSTransition() {
+ shouldPerformSmartspaceTransition = false
+
+ val mockLockscreenSmartspaceView = mock(View::class.java)
+ whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.VISIBLE)
+ keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
+
+ keyguardUnlockAnimationController.unlockToLauncherWithInWindowAnimations()
+
+ verify(mockLockscreenSmartspaceView, never()).visibility = View.INVISIBLE
+ }
+
+ @Test
fun unlockToLauncherWithInWindowAnimations_ssViewIsInvisible() {
val mockLockscreenSmartspaceView = mock(View::class.java)
whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.INVISIBLE)
@@ -591,7 +682,7 @@
private var allArgs: MutableList<T> = mutableListOf()
fun capture(predicate: Predicate<T>): T {
- return argThat{x: T ->
+ return argThat { x: T ->
if (predicate.test(x)) {
allArgs.add(x)
return@argThat true
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/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 7560a97..e3bdcd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -23,6 +23,7 @@
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.DialogTransitionAnimator
@@ -232,7 +233,8 @@
@Mock private lateinit var expandable: Expandable
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
- @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
+ @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
private lateinit var underTest: KeyguardQuickAffordanceInteractor
private lateinit var testScope: TestScope
@@ -327,6 +329,7 @@
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
logger = logger,
+ metricsLogger = metricsLogger,
devicePolicyManager = devicePolicyManager,
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index fd1bf54..591ce1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -23,6 +23,7 @@
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.DialogTransitionAnimator
@@ -232,7 +233,8 @@
@Mock private lateinit var expandable: Expandable
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
- @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
+ @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
private lateinit var underTest: KeyguardQuickAffordanceInteractor
private lateinit var testScope: TestScope
@@ -327,6 +329,7 @@
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
logger = logger,
+ metricsLogger = metricsLogger,
devicePolicyManager = devicePolicyManager,
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 3b96be4..fc7f693 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -23,6 +23,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
@@ -99,7 +100,8 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
- @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
+ @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper
@@ -237,6 +239,7 @@
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
logger = logger,
+ metricsLogger = metricsLogger,
devicePolicyManager = devicePolicyManager,
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index e89abf6..77977f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -23,6 +23,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
@@ -93,7 +94,8 @@
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
- @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
+ @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock
private lateinit var aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel
@@ -299,6 +301,7 @@
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
logger = logger,
+ metricsLogger = metricsLogger,
devicePolicyManager = devicePolicyManager,
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
index 7dd8028..f884b87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -25,8 +25,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
@@ -34,10 +32,8 @@
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
import org.junit.After
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -46,7 +42,6 @@
private lateinit var dialog: AlertDialog
- private val flags = mock<FeatureFlagsClassic>()
private val appName = "Test App"
private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
@@ -54,11 +49,6 @@
private val resIdSingleAppDisabled =
R.string.media_projection_entry_app_permission_dialog_single_app_disabled
- @Before
- fun setUp() {
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
- }
-
@After
fun teardown() {
if (::dialog.isInitialized) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 31652a5..f90e1e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -47,6 +47,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
@@ -60,7 +61,8 @@
@RunWith(AndroidJUnit4::class)
@RunWithLooper
class FooterActionsViewModelTest : SysuiTestCase() {
- private val testScope = TestScope()
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
private lateinit var utils: FooterActionsTestUtils
private val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
@@ -127,7 +129,7 @@
fun userSwitcher() = runTest {
val picture: Drawable = mock()
val userInfoController = FakeUserInfoController(FakeInfo(picture = picture))
- val settings = FakeGlobalSettings()
+ val settings = FakeGlobalSettings(testDispatcher)
val userId = 42
val userSwitcherControllerWrapper =
MockUserSwitcherControllerWrapper(currentUserName = "foo")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index 27b6ea6..74d9692 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -27,7 +27,6 @@
import com.android.internal.logging.MetricsLogger
import com.android.settingslib.notification.data.repository.FakeZenModeRepository
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -47,7 +46,6 @@
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
-import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -82,8 +80,6 @@
@Mock private lateinit var qsTileConfigProvider: QSTileConfigProvider
- @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
-
@Mock private lateinit var dialogDelegate: ModesDialogDelegate
private val inputHandler = FakeQSTileIntentUserInputHandler()
@@ -131,9 +127,7 @@
userActionInteractor =
ModesTileUserActionInteractor(
- EmptyCoroutineContext,
inputHandler,
- dialogTransitionAnimator,
dialogDelegate,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index ce1a885..8d84c3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -177,7 +177,6 @@
.thenReturn(false)
whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
.thenReturn(false)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
whenever(state.hasUserApprovedScreenRecording).thenReturn(false)
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
@@ -200,7 +199,6 @@
.thenReturn(false)
whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
.thenReturn(false)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(false)
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
screenRecordSwitch.isChecked = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 477c50b..6b16e78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -39,10 +39,8 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
-import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -52,12 +50,9 @@
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
-import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.DialogDelegate;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -110,7 +105,6 @@
private FakeFeatureFlags mFeatureFlags;
private RecordingController mController;
- private TestSystemUIDialogFactory mDialogFactory;
private static final int USER_ID = 10;
@@ -120,14 +114,6 @@
Context spiedContext = spy(mContext);
when(spiedContext.getUserId()).thenReturn(TEST_USER_ID);
- mDialogFactory = new TestSystemUIDialogFactory(
- mContext,
- Dependency.get(SystemUIDialogManager.class),
- Dependency.get(SysUiState.class),
- Dependency.get(BroadcastDispatcher.class),
- Dependency.get(DialogTransitionAnimator.class)
- );
-
mFeatureFlags = new FakeFeatureFlags();
when(mScreenCaptureDisabledDialogDelegate.createSysUIDialog())
.thenReturn(mScreenCaptureDisabledDialog);
@@ -251,7 +237,6 @@
@Test
public void testPoliciesFlagDisabled_screenCapturingNotAllowed_returnsNullDevicePolicyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
@@ -269,19 +254,7 @@
}
@Test
- public void testPartialScreenSharingDisabled_returnsLegacyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, false);
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
-
- Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
- mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
-
- assertThat(dialog).isEqualTo(mScreenRecordSystemUIDialog);
- }
-
- @Test
public void testPoliciesFlagEnabled_screenCapturingNotAllowed_returnsDevicePolicyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
@@ -293,7 +266,6 @@
@Test
public void testPoliciesFlagEnabled_screenCapturingAllowed_returnsNullDevicePolicyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
@@ -312,7 +284,6 @@
@Test
public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
@@ -324,32 +295,4 @@
/* hostUid= */ myUid(),
SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
}
-
- private static class TestSystemUIDialogFactory extends SystemUIDialog.Factory {
-
- @Nullable private DialogDelegate<SystemUIDialog> mLastDelegate;
- @Nullable private SystemUIDialog mLastCreatedDialog;
-
- TestSystemUIDialogFactory(
- Context context,
- SystemUIDialogManager systemUIDialogManager,
- SysUiState sysUiState,
- BroadcastDispatcher broadcastDispatcher,
- DialogTransitionAnimator dialogTransitionAnimator) {
- super(
- context,
- systemUIDialogManager,
- sysUiState,
- broadcastDispatcher,
- dialogTransitionAnimator);
- }
-
- @Override
- public SystemUIDialog create(SystemUIDialog.Delegate delegate) {
- SystemUIDialog dialog = super.create(delegate);
- mLastDelegate = delegate;
- mLastCreatedDialog = dialog;
- return dialog;
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index cc8d7d5..11b0bdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -28,7 +28,6 @@
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
@@ -51,7 +50,6 @@
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@@ -72,8 +70,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
-
val systemUIDialogFactory =
SystemUIDialog.Factory(
context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
index 24e8b18..5e07aef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
@@ -228,7 +228,7 @@
)
val quickSharePendingIntent =
quickShareAction.actionIntent.intent.extras!!.getParcelable(
- ScreenshotController.EXTRA_ACTION_INTENT,
+ SmartActionsReceiver.EXTRA_ACTION_INTENT,
PendingIntent::class.java
)
@@ -266,7 +266,7 @@
assertEquals(
immutablePendingIntent,
quickShareAction.actionIntent.intent.extras!!.getParcelable(
- ScreenshotController.EXTRA_ACTION_INTENT,
+ SmartActionsReceiver.EXTRA_ACTION_INTENT,
PendingIntent::class.java
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
index 471fdc0..9dc5cfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
@@ -16,8 +16,8 @@
package com.android.systemui.screenshot;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_TYPE;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
+import static com.android.systemui.screenshot.SmartActionsReceiver.EXTRA_ACTION_TYPE;
+import static com.android.systemui.screenshot.SmartActionsReceiver.EXTRA_ID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -57,7 +57,7 @@
MockitoAnnotations.initMocks(this);
mSmartActionsReceiver = new SmartActionsReceiver(mMockScreenshotSmartActions);
mIntent = new Intent(mContext, SmartActionsReceiver.class)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, mMockPendingIntent);
+ .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT, mMockPendingIntent);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 8d3a29a..a295981 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -42,10 +42,10 @@
@SmallTest
class TakeScreenshotExecutorTest : SysuiTestCase() {
- private val controller = mock<ScreenshotController>()
+ private val controller = mock<LegacyScreenshotController>()
private val notificationsController0 = mock<ScreenshotNotificationsController>()
private val notificationsController1 = mock<ScreenshotNotificationsController>()
- private val controllerFactory = mock<ScreenshotController.Factory>()
+ private val controllerFactory = mock<InteractiveScreenshotHandler.Factory>()
private val callback = mock<TakeScreenshotService.RequestCallback>()
private val notificationControllerFactory = mock<ScreenshotNotificationsController.Factory>()
@@ -287,7 +287,7 @@
fun onCloseSystemDialogsReceived_controllerHasPendingTransitions() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- whenever(controller.isPendingSharedTransition).thenReturn(true)
+ whenever(controller.isPendingSharedTransition()).thenReturn(true)
val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
index 9986205..a8d5008 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.screenshot.appclips;
import static android.app.Activity.RESULT_OK;
+import static android.app.ActivityManager.RunningTaskInfo;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_ACCEPTED;
@@ -32,7 +33,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IActivityTaskManager;
import android.app.assist.AssistContent;
import android.content.ComponentName;
@@ -103,7 +103,7 @@
private static final String BACKLINKS_TASK_APP_NAME = "Backlinks app";
private static final String BACKLINKS_TASK_PACKAGE_NAME = "backlinksTaskPackageName";
- private static final RootTaskInfo TASK_THAT_SUPPORTS_BACKLINKS =
+ private static final RunningTaskInfo TASK_THAT_SUPPORTS_BACKLINKS =
createTaskInfoForBacklinksTask();
private static final AssistContent ASSIST_CONTENT_FOR_BACKLINKS_TASK =
createAssistContentForBacklinksTask();
@@ -233,6 +233,10 @@
assertThat(backlinksData.getText().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
assertThat(backlinksData.getCompoundDrawablesRelative()[0]).isEqualTo(FAKE_DRAWABLE);
+ // Verify dropdown icon is not shown and there are no click listeners on text view.
+ assertThat(backlinksData.getCompoundDrawablesRelative()[2]).isNull();
+ assertThat(backlinksData.hasOnClickListeners()).isFalse();
+
CheckBox backlinksIncludeData = mActivity.findViewById(R.id.backlinks_include_data);
assertThat(backlinksIncludeData.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(backlinksIncludeData.getText().toString())
@@ -258,20 +262,71 @@
assertThat(backlinksData.getVisibility()).isEqualTo(View.GONE);
}
+ @Test
+ @EnableFlags(Flags.FLAG_APP_CLIPS_BACKLINKS)
+ public void appClipsLaunched_backlinks_multipleBacklinksAvailable_defaultShown()
+ throws RemoteException {
+ // Set up mocking for multiple backlinks.
+ ResolveInfo resolveInfo1 = createBacklinksTaskResolveInfo();
+
+ int taskId2 = BACKLINKS_TASK_ID + 2;
+ String package2 = BACKLINKS_TASK_PACKAGE_NAME + 2;
+ String appName2 = BACKLINKS_TASK_APP_NAME + 2;
+
+ ResolveInfo resolveInfo2 = createBacklinksTaskResolveInfo();
+ ActivityInfo activityInfo2 = resolveInfo2.activityInfo;
+ activityInfo2.name = appName2;
+ activityInfo2.packageName = package2;
+ activityInfo2.applicationInfo.packageName = package2;
+ RunningTaskInfo runningTaskInfo2 = createTaskInfoForBacklinksTask();
+ runningTaskInfo2.taskId = taskId2;
+ runningTaskInfo2.topActivity = new ComponentName(package2, "backlinksClass");
+ runningTaskInfo2.topActivityInfo = resolveInfo2.activityInfo;
+ runningTaskInfo2.baseIntent = new Intent().setComponent(runningTaskInfo2.topActivity);
+
+ when(mAtmService.getTasks(eq(Integer.MAX_VALUE), eq(false), eq(false),
+ mDisplayIdCaptor.capture()))
+ .thenReturn(List.of(TASK_THAT_SUPPORTS_BACKLINKS, runningTaskInfo2));
+ when(mPackageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn(resolveInfo1,
+ resolveInfo1, resolveInfo1, resolveInfo2, resolveInfo2, resolveInfo2);
+ when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
+
+ // Using same AssistContent data for both tasks.
+ mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, BACKLINKS_TASK_ID);
+ mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, taskId2);
+
+ // Mocking complete, trigger backlinks.
+ launchActivity();
+ waitForIdleSync();
+
+ // Verify default backlink shown to user and text view has on click listener.
+ TextView backlinksData = mActivity.findViewById(R.id.backlinks_data);
+ assertThat(backlinksData.getText().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
+ assertThat(backlinksData.hasOnClickListeners()).isTrue();
+
+ // Verify dropdown icon is not null.
+ assertThat(backlinksData.getCompoundDrawablesRelative()[2]).isNotNull();
+ }
+
private void setUpMocksForBacklinks() throws RemoteException {
- when(mAtmService.getAllRootTaskInfosOnDisplay(mDisplayIdCaptor.capture()))
+ when(mAtmService.getTasks(eq(Integer.MAX_VALUE), eq(false), eq(false),
+ mDisplayIdCaptor.capture()))
.thenReturn(List.of(TASK_THAT_SUPPORTS_BACKLINKS));
- doAnswer(invocation -> {
- AssistContentRequester.Callback callback = invocation.getArgument(1);
- callback.onAssistContentAvailable(ASSIST_CONTENT_FOR_BACKLINKS_TASK);
- return null;
- }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+ mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, BACKLINKS_TASK_ID);
when(mPackageManager
.resolveActivity(any(Intent.class), anyInt()))
.thenReturn(createBacklinksTaskResolveInfo());
when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
}
+ private void mockForAssistContent(AssistContent expected, int taskId) {
+ doAnswer(invocation -> {
+ AssistContentRequester.Callback callback = invocation.getArgument(1);
+ callback.onAssistContentAvailable(expected);
+ return null;
+ }).when(mAssistContentRequester).requestAssistContent(eq(taskId), any());
+ }
+
private void launchActivity() {
launchActivity(createResultReceiver(FAKE_CONSUMER));
}
@@ -319,8 +374,8 @@
return resolveInfo;
}
- private static RootTaskInfo createTaskInfoForBacklinksTask() {
- RootTaskInfo taskInfo = new RootTaskInfo();
+ private static RunningTaskInfo createTaskInfoForBacklinksTask() {
+ RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = BACKLINKS_TASK_ID;
taskInfo.isVisible = true;
taskInfo.isRunning = true;
@@ -328,7 +383,6 @@
taskInfo.topActivity = new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, "backlinksClass");
taskInfo.topActivityInfo = createBacklinksTaskResolveInfo().activityInfo;
taskInfo.baseIntent = new Intent().setComponent(taskInfo.topActivity);
- taskInfo.childTaskIds = new int[]{BACKLINKS_TASK_ID + 1};
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
return taskInfo;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
index 193d29c..178547e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
@@ -37,7 +37,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.ActivityTaskManager.RootTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
import android.app.IActivityTaskManager;
import android.app.assist.AssistContent;
import android.content.ClipData;
@@ -107,7 +107,7 @@
mPackageManagerIntentCaptor = ArgumentCaptor.forClass(Intent.class);
// Set up mocking for backlinks.
- when(mAtmService.getAllRootTaskInfosOnDisplay(DEFAULT_DISPLAY))
+ when(mAtmService.getTasks(Integer.MAX_VALUE, false, false, DEFAULT_DISPLAY))
.thenReturn(List.of(createTaskInfoForBacklinksTask()));
when(mPackageManager.resolveActivity(mPackageManagerIntentCaptor.capture(), anyInt()))
.thenReturn(createBacklinksTaskResolveInfo());
@@ -190,11 +190,7 @@
Uri expectedUri = Uri.parse("https://developers.android.com");
AssistContent contentWithUri = new AssistContent();
contentWithUri.setWebUri(expectedUri);
- doAnswer(invocation -> {
- AssistContentRequester.Callback callback = invocation.getArgument(1);
- callback.onAssistContentAvailable(contentWithUri);
- return null;
- }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+ mockForAssistContent(contentWithUri, BACKLINKS_TASK_ID);
mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
waitForIdleSync();
@@ -203,7 +199,7 @@
assertThat(queriedIntent.getData()).isEqualTo(expectedUri);
assertThat(queriedIntent.getAction()).isEqualTo(ACTION_VIEW);
- InternalBacklinksData result = mViewModel.getBacklinksLiveData().getValue();
+ InternalBacklinksData result = mViewModel.mSelectedBacklinksLiveData.getValue();
assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
ClipData clipData = result.getClipData();
ClipDescription resultDescription = clipData.getDescription();
@@ -211,6 +207,8 @@
assertThat(resultDescription.getMimeType(0)).isEqualTo(MIMETYPE_TEXT_URILIST);
assertThat(clipData.getItemCount()).isEqualTo(1);
assertThat(clipData.getItemAt(0).getUri()).isEqualTo(expectedUri);
+
+ assertThat(result).isEqualTo(mViewModel.getBacklinksLiveData().getValue().get(0));
}
@Test
@@ -218,12 +216,8 @@
Uri expectedUri = Uri.parse("https://developers.android.com");
AssistContent contentWithUri = new AssistContent();
contentWithUri.setWebUri(expectedUri);
+ mockForAssistContent(contentWithUri, BACKLINKS_TASK_ID);
resetPackageManagerMockingForUsingFallbackBacklinks();
- doAnswer(invocation -> {
- AssistContentRequester.Callback callback = invocation.getArgument(1);
- callback.onAssistContentAvailable(contentWithUri);
- return null;
- }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
waitForIdleSync();
@@ -236,11 +230,7 @@
Intent expectedIntent = new Intent().setPackage(BACKLINKS_TASK_PACKAGE_NAME);
AssistContent contentWithAppProvidedIntent = new AssistContent();
contentWithAppProvidedIntent.setIntent(expectedIntent);
- doAnswer(invocation -> {
- AssistContentRequester.Callback callback = invocation.getArgument(1);
- callback.onAssistContentAvailable(contentWithAppProvidedIntent);
- return null;
- }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+ mockForAssistContent(contentWithAppProvidedIntent, BACKLINKS_TASK_ID);
mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
waitForIdleSync();
@@ -248,7 +238,7 @@
Intent queriedIntent = mPackageManagerIntentCaptor.getValue();
assertThat(queriedIntent.getPackage()).isEqualTo(expectedIntent.getPackage());
- InternalBacklinksData result = mViewModel.getBacklinksLiveData().getValue();
+ InternalBacklinksData result = mViewModel.mSelectedBacklinksLiveData.getValue();
assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
ClipData clipData = result.getClipData();
ClipDescription resultDescription = clipData.getDescription();
@@ -263,12 +253,8 @@
Intent expectedIntent = new Intent().setPackage(BACKLINKS_TASK_PACKAGE_NAME);
AssistContent contentWithAppProvidedIntent = new AssistContent();
contentWithAppProvidedIntent.setIntent(expectedIntent);
+ mockForAssistContent(contentWithAppProvidedIntent, BACKLINKS_TASK_ID);
resetPackageManagerMockingForUsingFallbackBacklinks();
- doAnswer(invocation -> {
- AssistContentRequester.Callback callback = invocation.getArgument(1);
- callback.onAssistContentAvailable(contentWithAppProvidedIntent);
- return null;
- }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
waitForIdleSync();
@@ -278,11 +264,7 @@
@Test
public void triggerBacklinks_shouldUpdateBacklinks_withMainLauncherIntent() {
- doAnswer(invocation -> {
- AssistContentRequester.Callback callback = invocation.getArgument(1);
- callback.onAssistContentAvailable(EMPTY_ASSIST_CONTENT);
- return null;
- }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+ mockForAssistContent(EMPTY_ASSIST_CONTENT, BACKLINKS_TASK_ID);
mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
waitForIdleSync();
@@ -298,15 +280,12 @@
@Test
public void triggerBacklinks_withNonResolvableMainLauncherIntent_noBacklinksAvailable() {
reset(mPackageManager);
- doAnswer(invocation -> {
- AssistContentRequester.Callback callback = invocation.getArgument(1);
- callback.onAssistContentAvailable(EMPTY_ASSIST_CONTENT);
- return null;
- }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+ mockForAssistContent(EMPTY_ASSIST_CONTENT, BACKLINKS_TASK_ID);
mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
waitForIdleSync();
+ assertThat(mViewModel.mSelectedBacklinksLiveData.getValue()).isNull();
assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull();
}
@@ -314,14 +293,15 @@
public void triggerBacklinks_nonStandardActivityIgnored_noBacklinkAvailable()
throws RemoteException {
reset(mAtmService);
- RootTaskInfo taskInfo = createTaskInfoForBacklinksTask();
+ RunningTaskInfo taskInfo = createTaskInfoForBacklinksTask();
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
- when(mAtmService.getAllRootTaskInfosOnDisplay(DEFAULT_DISPLAY))
+ when(mAtmService.getTasks(Integer.MAX_VALUE, false, false, DEFAULT_DISPLAY))
.thenReturn(List.of(taskInfo));
mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
waitForIdleSync();
+ assertThat(mViewModel.mSelectedBacklinksLiveData.getValue()).isNull();
assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull();
}
@@ -330,9 +310,68 @@
mViewModel.triggerBacklinks(Set.of(BACKLINKS_TASK_ID), DEFAULT_DISPLAY);
waitForIdleSync();
+ assertThat(mViewModel.mSelectedBacklinksLiveData.getValue()).isNull();
assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull();
}
+ @Test
+ public void triggerBacklinks_multipleAppsOnScreen_multipleBacklinksAvailable()
+ throws RemoteException {
+ // Set up mocking for multiple backlinks.
+ reset(mAtmService, mPackageManager);
+ RunningTaskInfo runningTaskInfo1 = createTaskInfoForBacklinksTask();
+ ResolveInfo resolveInfo1 = createBacklinksTaskResolveInfo();
+
+ int taskId2 = BACKLINKS_TASK_ID + 2;
+ String package2 = BACKLINKS_TASK_PACKAGE_NAME + 2;
+ String appName2 = BACKLINKS_TASK_APP_NAME + 2;
+
+ ResolveInfo resolveInfo2 = createBacklinksTaskResolveInfo();
+ ActivityInfo activityInfo2 = resolveInfo2.activityInfo;
+ activityInfo2.name = appName2;
+ activityInfo2.packageName = package2;
+ activityInfo2.applicationInfo.packageName = package2;
+ RunningTaskInfo runningTaskInfo2 = createTaskInfoForBacklinksTask();
+ runningTaskInfo2.taskId = taskId2;
+ runningTaskInfo2.topActivity = new ComponentName(package2, "backlinksClass");
+ runningTaskInfo2.topActivityInfo = resolveInfo2.activityInfo;
+ runningTaskInfo2.baseIntent = new Intent().setComponent(runningTaskInfo2.topActivity);
+
+ // For each task, the logic queries PM 3 times, twice for verifying if an app can be
+ // launched via launcher and once with the data provided in backlink intent.
+ when(mPackageManager.resolveActivity(any(), anyInt())).thenReturn(resolveInfo1,
+ resolveInfo1, resolveInfo1, resolveInfo2, resolveInfo2, resolveInfo2);
+ when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
+ when(mAtmService.getTasks(Integer.MAX_VALUE, false, false, DEFAULT_DISPLAY))
+ .thenReturn(List.of(runningTaskInfo1, runningTaskInfo2));
+
+ // Using app provided web uri for the first backlink.
+ Uri expectedUri = Uri.parse("https://developers.android.com");
+ AssistContent contentWithUri = new AssistContent();
+ contentWithUri.setWebUri(expectedUri);
+ mockForAssistContent(contentWithUri, BACKLINKS_TASK_ID);
+
+ // Using app provided intent for the second backlink.
+ Intent expectedIntent = new Intent().setPackage(package2);
+ AssistContent contentWithAppProvidedIntent = new AssistContent();
+ contentWithAppProvidedIntent.setIntent(expectedIntent);
+ mockForAssistContent(contentWithAppProvidedIntent, taskId2);
+
+ // Set up complete, trigger the backlinks action.
+ mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
+ waitForIdleSync();
+
+ // Verify two backlinks are received and the first backlink is set as default selected.
+ assertThat(mViewModel.mSelectedBacklinksLiveData.getValue().getClipData().getItemAt(
+ 0).getUri()).isEqualTo(expectedUri);
+ List<InternalBacklinksData> actualBacklinks = mViewModel.getBacklinksLiveData().getValue();
+ assertThat(actualBacklinks).hasSize(2);
+ assertThat(actualBacklinks.get(0).getClipData().getItemAt(0).getUri())
+ .isEqualTo(expectedUri);
+ assertThat(actualBacklinks.get(1).getClipData().getItemAt(0).getIntent())
+ .isEqualTo(expectedIntent);
+ }
+
private void resetPackageManagerMockingForUsingFallbackBacklinks() {
ResolveInfo backlinksTaskResolveInfo = createBacklinksTaskResolveInfo();
reset(mPackageManager);
@@ -350,7 +389,7 @@
}
private void verifyMainLauncherBacklinksIntent() {
- InternalBacklinksData result = mViewModel.getBacklinksLiveData().getValue();
+ InternalBacklinksData result = mViewModel.mSelectedBacklinksLiveData.getValue();
assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
ClipData clipData = result.getClipData();
@@ -368,6 +407,14 @@
new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, BACKLINKS_TASK_APP_NAME));
}
+ private void mockForAssistContent(AssistContent expected, int taskId) {
+ doAnswer(invocation -> {
+ AssistContentRequester.Callback callback = invocation.getArgument(1);
+ callback.onAssistContentAvailable(expected);
+ return null;
+ }).when(mAssistContentRequester).requestAssistContent(eq(taskId), any());
+ }
+
private static ResolveInfo createBacklinksTaskResolveInfo() {
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.applicationInfo = new ApplicationInfo();
@@ -379,8 +426,8 @@
return resolveInfo;
}
- private static RootTaskInfo createTaskInfoForBacklinksTask() {
- RootTaskInfo taskInfo = new RootTaskInfo();
+ private static RunningTaskInfo createTaskInfoForBacklinksTask() {
+ RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = BACKLINKS_TASK_ID;
taskInfo.isVisible = true;
taskInfo.isRunning = true;
@@ -388,7 +435,6 @@
taskInfo.topActivity = new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, "backlinksClass");
taskInfo.topActivityInfo = createBacklinksTaskResolveInfo().activityInfo;
taskInfo.baseIntent = new Intent().setComponent(taskInfo.topActivity);
- taskInfo.childTaskIds = new int[]{BACKLINKS_TASK_ID + 1};
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
return taskInfo;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 967df39..5de31d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -429,6 +429,7 @@
fun gestureExclusionZone_setAfterInit() =
with(kosmos) {
testScope.runTest {
+ whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR)
goToScene(CommunalScenes.Communal)
assertThat(containerView.systemGestureExclusionRects)
@@ -450,10 +451,37 @@
}
@Test
+ @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
+ fun gestureExclusionZone_setAfterInit_rtl() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL)
+ goToScene(CommunalScenes.Communal)
+
+ assertThat(containerView.systemGestureExclusionRects)
+ .containsExactly(
+ Rect(
+ /* left= */ 0,
+ /* top= */ TOP_SWIPE_REGION_WIDTH,
+ /* right= */ CONTAINER_WIDTH,
+ /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH
+ ),
+ Rect(
+ /* left= */ 0,
+ /* top= */ 0,
+ /* right= */ CONTAINER_WIDTH,
+ /* bottom= */ CONTAINER_HEIGHT
+ )
+ )
+ }
+ }
+
+ @Test
@EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
fun gestureExclusionZone_setAfterInit_backGestureEnabled() =
with(kosmos) {
testScope.runTest {
+ whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR)
goToScene(CommunalScenes.Communal)
assertThat(containerView.systemGestureExclusionRects)
@@ -475,6 +503,32 @@
}
@Test
+ @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
+ fun gestureExclusionZone_setAfterInit_backGestureEnabled_rtl() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL)
+ goToScene(CommunalScenes.Communal)
+
+ assertThat(containerView.systemGestureExclusionRects)
+ .containsExactly(
+ Rect(
+ /* left= */ FAKE_INSETS.left,
+ /* top= */ TOP_SWIPE_REGION_WIDTH,
+ /* right= */ CONTAINER_WIDTH - FAKE_INSETS.right,
+ /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH
+ ),
+ Rect(
+ /* left= */ FAKE_INSETS.left,
+ /* top= */ 0,
+ /* right= */ CONTAINER_WIDTH,
+ /* bottom= */ CONTAINER_HEIGHT
+ )
+ )
+ }
+ }
+
+ @Test
fun gestureExclusionZone_unsetWhenShadeOpen() =
with(kosmos) {
testScope.runTest {
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/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
deleted file mode 100644
index 3908529..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- *
- */
-
-package com.android.systemui.statusbar.notification.domain.interactor
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-class SeenNotificationsInteractorTest : SysuiTestCase() {
-
- private val repository = ActiveNotificationListRepository()
- private val underTest = SeenNotificationsInteractor(repository)
-
- @Test
- fun testNoFilteredOutSeenNotifications() = runTest {
- val hasFilteredOutSeenNotifications by
- collectLastValue(underTest.hasFilteredOutSeenNotifications)
-
- underTest.setHasFilteredOutSeenNotifications(false)
-
- assertThat(hasFilteredOutSeenNotifications).isFalse()
- }
-
- @Test
- fun testHasFilteredOutSeenNotifications() = runTest {
- val hasFilteredOutSeenNotifications by
- collectLastValue(underTest.hasFilteredOutSeenNotifications)
-
- underTest.setHasFilteredOutSeenNotifications(true)
-
- assertThat(hasFilteredOutSeenNotifications).isTrue()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 1060b62..c36a046 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -91,7 +91,6 @@
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.init.NotificationsController;
@@ -188,12 +187,8 @@
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
-
- private final ActiveNotificationListRepository mActiveNotificationsRepository =
- new ActiveNotificationListRepository();
-
private final SeenNotificationsInteractor mSeenNotificationsInteractor =
- new SeenNotificationsInteractor(mActiveNotificationsRepository);
+ mKosmos.getSeenNotificationsInteractor();
private NotificationStackScrollLayoutController mController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index fd2dead..e670884 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -334,6 +334,7 @@
/* typeVisibilityMap = */ booleanArrayOf(),
/* isRound = */ false,
/* forceConsumingTypes = */ 0,
+ /* forceConsumingCaptionBar = */ false,
/* suppressScrimTypes = */ 0,
/* displayCutout = */ DisplayCutout.NO_CUTOUT,
/* roundedCorners = */ RoundedCorners.NO_ROUNDED_CORNERS,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 31f93b4..af5e60e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -94,6 +95,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -166,6 +168,7 @@
@Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
@Mock private DeviceEntryInteractor mDeviceEntryInteractor;
+ @Mock private SceneInteractor mSceneInteractor;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
@@ -233,7 +236,7 @@
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
- () -> mock(SceneInteractor.class),
+ () -> mSceneInteractor,
mock(StatusBarKeyguardViewManagerInteractor.class),
() -> mDeviceEntryInteractor) {
@Override
@@ -270,21 +273,23 @@
}
@Test
- public void showBouncer_onlyWhenShowing() {
+ public void showPrimaryBouncer_onlyWhenShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
+ verify(mSceneInteractor, never()).changeScene(any(), any());
}
@Test
- public void showBouncer_notWhenBouncerAlreadyShowing() {
+ public void showPrimaryBouncer_notWhenBouncerAlreadyShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.Password);
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
+ verify(mSceneInteractor, never()).changeScene(any(), any());
}
@Test
@@ -753,7 +758,7 @@
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
- () -> mock(SceneInteractor.class),
+ () -> mSceneInteractor,
mock(StatusBarKeyguardViewManagerInteractor.class),
() -> mDeviceEntryInteractor) {
@Override
@@ -1104,9 +1109,9 @@
@Test
@EnableSceneContainer
- public void showPrimaryBouncer_attemptDeviceEntry() {
+ public void showPrimaryBouncer() {
mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
- verify(mDeviceEntryInteractor).attemptDeviceEntry();
+ verify(mSceneInteractor).changeScene(eq(Scenes.Bouncer), anyString());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
index 917e5b8..0641e17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
@@ -62,7 +62,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- settings = FakeGlobalSettings()
+ settings = FakeGlobalSettings(testContext)
whenever(telephonyManager.emergencyCallbackMode).thenReturn(false)
whenever(subscriptionManager.activeSubscriptionIdList).thenReturn(intArrayOf())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
new file mode 100644
index 0000000..bf0a39b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.policy.ui.dialog
+
+import android.app.Dialog
+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.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.mockActivityTransitionAnimatorController
+import com.android.systemui.animation.mockDialogTransitionAnimator
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.mainCoroutineContext
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.runOnMainThreadAndWaitForIdleSync
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.systemUIDialogFactory
+import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+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.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModesDialogDelegateTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val activityStarter = kosmos.activityStarter
+ private val mockDialogTransitionAnimator = kosmos.mockDialogTransitionAnimator
+ private val mockAnimationController = kosmos.mockActivityTransitionAnimatorController
+ private lateinit var underTest: ModesDialogDelegate
+
+ @Before
+ fun setup() {
+ whenever(
+ mockDialogTransitionAnimator.createActivityTransitionController(
+ any<SystemUIDialog>(),
+ eq(null)
+ )
+ )
+ .thenReturn(mockAnimationController)
+
+ underTest =
+ ModesDialogDelegate(
+ kosmos.systemUIDialogFactory,
+ mockDialogTransitionAnimator,
+ activityStarter,
+ { kosmos.modesDialogViewModel },
+ kosmos.mainCoroutineContext,
+ )
+ }
+
+ @Test
+ fun launchFromDialog_whenDialogNotOpen() {
+ val intent: Intent = mock()
+
+ runOnMainThreadAndWaitForIdleSync { underTest.launchFromDialog(intent) }
+
+ verify(activityStarter)
+ .startActivity(eq(intent), eq(true), eq<ActivityTransitionAnimator.Controller?>(null))
+ }
+
+ @Test
+ fun launchFromDialog_whenDialogOpen() =
+ testScope.runTest {
+ val intent: Intent = mock()
+ lateinit var dialog: Dialog
+
+ runOnMainThreadAndWaitForIdleSync {
+ kosmos.applicationCoroutineScope.launch { dialog = underTest.showDialog() }
+ runCurrent()
+ underTest.launchFromDialog(intent)
+ }
+
+ verify(mockDialogTransitionAnimator)
+ .createActivityTransitionController(any<Dialog>(), eq(null))
+ verify(activityStarter).startActivity(eq(intent), eq(true), eq(mockAnimationController))
+
+ runOnMainThreadAndWaitForIdleSync { dialog.dismiss() }
+ }
+
+ @Test
+ fun dismiss_clearsDialogReference() {
+ val dialog = runOnMainThreadAndWaitForIdleSync { underTest.createDialog() }
+
+ assertThat(underTest.currentDialog).isEqualTo(dialog)
+
+ runOnMainThreadAndWaitForIdleSync {
+ dialog.show()
+ dialog.dismiss()
+ }
+
+ assertThat(underTest.currentDialog).isNull()
+ }
+}
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/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
index 49aedcc..bebf1cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
@@ -36,7 +36,6 @@
import android.media.AudioManager;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
-import android.util.Pair;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -70,6 +69,8 @@
private CsdWarningDialog mDialog;
private static final String DISMISS_CSD_NOTIFICATION =
"com.android.systemui.volume.DISMISS_CSD_NOTIFICATION";
+ private final Optional<ImmutableList<CsdWarningAction>> mEmptyActions =
+ Optional.of(ImmutableList.of());
@Before
public void setup() {
@@ -87,7 +88,7 @@
// instantiate directly instead of via factory; we don't want executor to be @Background
mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext,
mAudioManager, mNotificationManager, executor, null,
- Optional.of(ImmutableList.of(new Pair("", new Intent()))),
+ mEmptyActions,
mFakeBroadcastDispatcher);
mDialog.show();
@@ -104,7 +105,7 @@
FakeExecutor executor = new FakeExecutor(new FakeSystemClock());
mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
mAudioManager, mNotificationManager, executor, null,
- Optional.of(ImmutableList.of(new Pair("", new Intent()))),
+ mEmptyActions,
mFakeBroadcastDispatcher);
mDialog.show();
@@ -121,7 +122,7 @@
.setPackage(mContext.getPackageName());
mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
mAudioManager, mNotificationManager, executor, null,
- Optional.of(ImmutableList.of(new Pair("Undo", undoIntent))),
+ Optional.of(ImmutableList.of(new CsdWarningAction("Undo", undoIntent, false))),
mFakeBroadcastDispatcher);
when(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)).thenReturn(25);
@@ -148,7 +149,7 @@
.setPackage(mContext.getPackageName());
mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
mAudioManager, mNotificationManager, executor, null,
- Optional.of(ImmutableList.of(new Pair("Undo", undoIntent))),
+ Optional.of(ImmutableList.of(new CsdWarningAction("Undo", undoIntent, false))),
mFakeBroadcastDispatcher);
Intent dismissIntent = new Intent(DISMISS_CSD_NOTIFICATION)
.setPackage(mContext.getPackageName());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index b5cbf59..caa1779 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -44,7 +44,6 @@
import static org.mockito.Mockito.when;
import android.app.KeyguardManager;
-import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -57,7 +56,6 @@
import android.provider.Settings;
import android.testing.TestableLooper;
import android.util.Log;
-import android.util.Pair;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -166,7 +164,7 @@
new CsdWarningDialog.Factory() {
@Override
public CsdWarningDialog create(int warningType, Runnable onCleanup,
- Optional<ImmutableList<Pair<String, Intent>>> actionIntents) {
+ Optional<ImmutableList<CsdWarningAction>> actionIntents) {
return mCsdWarningDialog;
}
};
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/android/app/role/RoleManagerKosmos.kt
similarity index 67%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
rename to packages/SystemUI/tests/utils/src/android/app/role/RoleManagerKosmos.kt
index 37c9552..356bc86 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/app/role/RoleManagerKosmos.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package android.app.role
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
-import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
+import com.android.systemui.util.mockito.mock
-val Kosmos.partitionedGridLayout by
- Kosmos.Fixture { PartitionedGridLayout(partitionedGridViewModel) }
+val Kosmos.mockRoleManager: RoleManager by Kosmos.Fixture { mock() }
+
+var Kosmos.roleManager: RoleManager by Kosmos.Fixture { mockRoleManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index a124b34..27a2cab 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -271,10 +271,14 @@
}
protected void waitForIdleSync() {
- if (mHandler == null) {
- mHandler = new Handler(Looper.getMainLooper());
+ if (isRobolectricTest()) {
+ mRealInstrumentation.waitForIdleSync();
+ } else {
+ if (mHandler == null) {
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+ waitForIdleSync(mHandler);
}
- waitForIdleSync(mHandler);
}
protected void waitForUiOffloadThread() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
index 37c9552..1f04a44 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.systemui.activatable
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
-import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
-val Kosmos.partitionedGridLayout by
- Kosmos.Fixture { PartitionedGridLayout(partitionedGridViewModel) }
+/** Activates [activatable] for the duration of the test. */
+fun Activatable.activateIn(testScope: TestScope) {
+ testScope.backgroundScope.launch { activate() }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
index b23767e..5ac41ec 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
@@ -18,6 +18,10 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mockActivityTransitionAnimatorController by
+ Kosmos.Fixture { mock<ActivityTransitionAnimator.Controller>() }
val Kosmos.activityTransitionAnimator by
Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSceneRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSceneRepositoryKosmos.kt
index a7a18a0..ef297d2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSceneRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSceneRepositoryKosmos.kt
@@ -20,7 +20,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
-val Kosmos.fakeCommunalSceneRepository by Fixture {
+var Kosmos.fakeCommunalSceneRepository by Fixture {
FakeCommunalSceneRepository(applicationScope = applicationCoroutineScope)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
index 28355e1..eff99e04 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
@@ -16,10 +16,9 @@
package com.android.systemui.haptics.qs
-import com.android.systemui.classifier.falsingManager
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.policy.keyguardStateController
val Kosmos.qsLongPressEffect by
- Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardStateController, falsingManager) }
+ Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardStateController) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
index 8e4461d..444baa0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.inputmethod.data.repository
+import android.os.UserHandle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.inputmethod.data.model.InputMethodModel
import kotlinx.coroutines.flow.Flow
@@ -40,14 +41,15 @@
}
override suspend fun enabledInputMethods(
- userId: Int,
- fetchSubtypes: Boolean,
+ user: UserHandle,
+ fetchSubtypes: Boolean
): Flow<InputMethodModel> {
- return usersToEnabledInputMethods[userId] ?: flowOf()
+ return usersToEnabledInputMethods[user.identifier] ?: flowOf()
}
- override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> =
- selectedInputMethodSubtypes
+ override suspend fun selectedInputMethodSubtypes(
+ user: UserHandle,
+ ): List<InputMethodModel.Subtype> = selectedInputMethodSubtypes
override suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean) {
inputMethodPickerShownDisplayId = displayId
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index c423b62..c2a03d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyboard.shortcut
+import android.app.role.mockRoleManager
import android.content.applicationContext
import android.content.res.mainResources
import android.hardware.input.fakeInputManager
@@ -41,6 +42,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
import com.android.systemui.settings.displayTracker
+import com.android.systemui.settings.fakeUserTracker
var Kosmos.shortcutHelperAppCategoriesShortcutsSource: KeyboardShortcutGroupsSource by
Kosmos.Fixture {
@@ -117,6 +119,8 @@
val Kosmos.shortcutHelperViewModel by
Kosmos.Fixture {
ShortcutHelperViewModel(
+ mockRoleManager,
+ fakeUserTracker,
applicationCoroutineScope,
testDispatcher,
shortcutHelperStateInteractor,
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/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 0666820..8614fc9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -60,6 +60,7 @@
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shadeController
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.scrimController
@@ -98,6 +99,7 @@
val communalRepository by lazy { kosmos.fakeCommunalSceneRepository }
val communalTransitionViewModel by lazy { kosmos.communalTransitionViewModel }
val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
+ val seenNotificationsInteractor by lazy { kosmos.seenNotificationsInteractor }
val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
val keyguardBouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
val keyguardInteractor by lazy { kosmos.keyguardInteractor }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
index 5568c6c..34e99d3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
@@ -20,24 +20,13 @@
import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
-import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
val Kosmos.gridLayoutTypeInteractor by
Kosmos.Fixture { GridLayoutTypeInteractor(gridLayoutTypeRepository) }
val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by
- Kosmos.Fixture {
- mapOf(
- Pair(PartitionedGridLayoutType, partitionedGridLayout),
- Pair(InfiniteGridLayoutType, infiniteGridLayout)
- )
- }
+ Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) }
var Kosmos.gridConsistencyInteractorsMap: Map<GridLayoutType, GridTypeConsistencyInteractor> by
- Kosmos.Fixture {
- mapOf(
- Pair(PartitionedGridLayoutType, noopGridConsistencyInteractor),
- Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)
- )
- }
+ Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
index 6625bb5..9481fca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
@@ -20,7 +20,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
-import com.android.systemui.qs.panels.domain.interactor.partitionedGridLayout
+import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
val Kosmos.tileGridViewModel by
@@ -29,7 +29,7 @@
gridLayoutTypeInteractor,
gridLayoutMap,
currentTilesInteractor,
- partitionedGridLayout,
+ infiniteGridLayout,
applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
new file mode 100644
index 0000000..2ecfb45
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.modes.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
+import javax.inject.Provider
+
+val Kosmos.modesTileUserActionInteractor: ModesTileUserActionInteractor by
+ Kosmos.Fixture {
+ ModesTileUserActionInteractor(
+ qsTileIntentUserInputHandler,
+ Provider { modesDialogDelegate }.get(),
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
index c56c56c..299b22e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
@@ -17,7 +17,6 @@
package com.android.systemui.qs.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.overlayShadeViewModel
@@ -27,6 +26,5 @@
shadeInteractor = shadeInteractor,
overlayShadeViewModel = overlayShadeViewModel,
quickSettingsContainerViewModel = quickSettingsContainerViewModel,
- applicationScope = applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index fff3b14..dd93141 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -2,7 +2,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.shared.model.FakeScene
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
@@ -18,16 +17,7 @@
)
}
-val Kosmos.fakeScenes by Fixture {
- sceneKeys
- .map { key ->
- FakeScene(
- scope = testScope.backgroundScope,
- key = key,
- )
- }
- .toSet()
-}
+val Kosmos.fakeScenes by Fixture { sceneKeys.map { key -> FakeScene(key) }.toSet() }
val Kosmos.scenes by Fixture { fakeScenes }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
index eeaa9db..64e3526 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
@@ -19,16 +19,12 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow
-import kotlinx.coroutines.flow.stateIn
class FakeScene(
- val scope: CoroutineScope,
override val key: SceneKey,
) : Scene {
var isDestinationScenesBeingCollected = false
@@ -40,11 +36,6 @@
.receiveAsFlow()
.onStart { isDestinationScenesBeingCollected = true }
.onCompletion { isDestinationScenesBeingCollected = false }
- .stateIn(
- scope = scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = emptyMap(),
- )
suspend fun setDestinationScenes(value: Map<UserAction, UserActionResult>) {
destinationScenesChannel.send(value)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
index 989c3a5..2c5a0f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.qs.footerActionsController
import com.android.systemui.qs.footerActionsViewModelFactory
@@ -30,7 +29,6 @@
val Kosmos.shadeSceneViewModel: ShadeSceneViewModel by
Kosmos.Fixture {
ShadeSceneViewModel(
- applicationScope = applicationCoroutineScope,
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsSceneAdapter,
brightnessMirrorViewModel = brightnessMirrorViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
index 77d97bb..933ebf0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
@@ -18,23 +18,19 @@
import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
-import com.android.systemui.util.settings.fakeSettings
var Kosmos.lockScreenMinimalismCoordinator by
Kosmos.Fixture {
LockScreenMinimalismCoordinator(
- bgDispatcher = testDispatcher,
dumpManager = dumpManager,
headsUpInteractor = headsUpNotificationInteractor,
logger = lockScreenMinimalismCoordinatorLogger,
scope = testScope.backgroundScope,
- secureSettings = fakeSettings,
seenNotificationsInteractor = seenNotificationsInteractor,
statusBarStateController = statusBarStateController,
shadeInteractor = shadeInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
index c1e0419..b19e221 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
@@ -16,12 +16,32 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import android.os.UserHandle
+import android.provider.Settings
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.util.settings.fakeSettings
val Kosmos.seenNotificationsInteractor by Fixture {
SeenNotificationsInteractor(
+ bgDispatcher = testDispatcher,
notificationListRepository = activeNotificationListRepository,
+ secureSettings = fakeSettings,
)
}
+
+var Kosmos.lockScreenShowOnlyUnseenNotificationsSetting: Boolean
+ get() =
+ fakeSettings.getIntForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ UserHandle.USER_CURRENT,
+ ) == 1
+ set(value) {
+ fakeSettings.putIntForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ if (value) 1 else 2,
+ UserHandle.USER_CURRENT,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index b8dec31..0b309b5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -319,14 +319,9 @@
// NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be
// set, but we do not want to override an existing value that is needed by a specific test.
- val rowFuture: SettableFuture<ExpandableNotificationRow> = SettableFuture.create()
val rowInflaterTask =
RowInflaterTask(mFakeSystemClock, Mockito.mock(RowInflaterTaskLogger::class.java))
- rowInflaterTask.inflate(context, null, entry, MoreExecutors.directExecutor()) { inflatedRow
- ->
- rowFuture.set(inflatedRow)
- }
- val row = rowFuture.get(1, TimeUnit.SECONDS)
+ val row = rowInflaterTask.inflateSynchronously(context, null, entry)
entry.row = row
mIconManager.createIcons(entry)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
new file mode 100644
index 0000000..99bb479
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.ui.dialog
+
+import com.android.systemui.animation.dialogTransitionAnimator
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.mainCoroutineContext
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.statusbar.phone.systemUIDialogFactory
+import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mockModesDialogDelegate by Kosmos.Fixture { mock<ModesDialogDelegate>() }
+
+var Kosmos.modesDialogDelegate: ModesDialogDelegate by
+ Kosmos.Fixture {
+ ModesDialogDelegate(
+ systemUIDialogFactory,
+ dialogTransitionAnimator,
+ activityStarter,
+ { modesDialogViewModel },
+ mainCoroutineContext,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt
new file mode 100644
index 0000000..00020f8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
+
+import android.content.mockedContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
+import javax.inject.Provider
+
+val Kosmos.modesDialogViewModel: ModesDialogViewModel by
+ Kosmos.Fixture {
+ ModesDialogViewModel(
+ mockedContext,
+ zenModeInteractor,
+ testDispatcher,
+ Provider { modesDialogDelegate }.get(),
+ )
+ }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index e2eb09f..8d89cc1 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -182,6 +182,9 @@
// want to verify they're unbundled. The "impl" library above is what
// ships inside the Ravenwood environment to actually drive any API
// access to implementation details.
+// This library needs to be statically linked to mainline tests as well,
+// which need to be able to run on multiple API levels, so we can't use
+// test APIs in this module.
java_library {
name: "ravenwood-junit",
srcs: [
@@ -189,7 +192,7 @@
"junit-stub-src/**/*.java",
"junit-flag-src/**/*.java",
],
- sdk_version: "test_current",
+ sdk_version: "module_current",
static_libs: [
"ravenwood-runtime-common",
"ravenwood-runtime-common-device",
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 68b5aeb..825c91a 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -18,7 +18,7 @@
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.SYSTEM_UID;
-import static android.os.UserHandle.USER_SYSTEM;
+import static android.os.UserHandle.SYSTEM;
import static org.junit.Assert.fail;
@@ -115,7 +115,7 @@
private static final AtomicInteger sNextPid = new AtomicInteger(100);
- int mCurrentUser = USER_SYSTEM;
+ int mCurrentUser = SYSTEM.getIdentifier();
/**
* Unless the test author requests differently, run as "nobody", and give each collection of
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
index 8a2bc1d..f7a59a4b 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
@@ -26,7 +26,6 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Slog;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -60,6 +59,7 @@
private final Set<AccessibilityHierarchyCheck> mHierarchyChecks;
private final ATFHierarchyBuilder mATFHierarchyBuilder;
private final Set<AccessibilityCheckResultReported> mCachedResults = new HashSet<>();
+
@VisibleForTesting
final A11yCheckerTimer mTimer = new A11yCheckerTimer();
@@ -86,10 +86,8 @@
*/
@RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
public Set<AccessibilityCheckResultReported> maybeRunA11yChecker(
- List<AccessibilityNodeInfo> nodes,
- @Nullable AccessibilityEvent accessibilityEvent,
- ComponentName sourceComponentName,
- @UserIdInt int userId) {
+ List<AccessibilityNodeInfo> nodes, @Nullable String sourceEventClassName,
+ ComponentName a11yServiceComponentName, @UserIdInt int userId) {
if (!shouldRunA11yChecker()) {
return Set.of();
}
@@ -108,14 +106,13 @@
List<AccessibilityHierarchyCheckResult> checkResults = runChecksOnNode(nodeInfo);
Set<AccessibilityCheckResultReported> filteredResults =
AccessibilityCheckerUtils.processResults(nodeInfo, checkResults,
- accessibilityEvent, mPackageManager, sourceComponentName);
+ sourceEventClassName, mPackageManager, a11yServiceComponentName);
allResults.addAll(filteredResults);
}
mCachedResults.addAll(allResults);
} catch (RuntimeException e) {
Slog.e(LOG_TAG, "An unknown error occurred while running a11y checker.", e);
}
-
return allResults;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
index 4171108..fa0fed2 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
@@ -22,7 +22,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Slog;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -96,7 +95,7 @@
static Set<AccessibilityCheckResultReported> processResults(
AccessibilityNodeInfo nodeInfo,
List<AccessibilityHierarchyCheckResult> checkResults,
- @Nullable AccessibilityEvent accessibilityEvent,
+ @Nullable String activityClassName,
PackageManager packageManager,
ComponentName a11yServiceComponentName) {
String appPackageName = nodeInfo.getPackageName().toString();
@@ -110,7 +109,8 @@
.setPackageName(appPackageName)
.setAppVersionCode(getAppVersionCode(packageManager, appPackageName))
.setUiElementPath(nodePath)
- .setActivityName(getActivityName(packageManager, accessibilityEvent))
+ .setActivityName(
+ getActivityName(packageManager, appPackageName, activityClassName))
.setWindowTitle(getWindowTitle(nodeInfo))
.setSourceComponentName(a11yServiceComponentName.flattenToString())
.setSourceVersionCode(
@@ -140,31 +140,23 @@
}
/**
- * Returns the simple class name of the Activity providing the cache update, if available,
+ * Returns the simple class name of the Activity associated with the window, if available,
* or an empty String if not.
*/
@VisibleForTesting
static String getActivityName(
- PackageManager packageManager, @Nullable AccessibilityEvent accessibilityEvent) {
- if (accessibilityEvent == null) {
+ PackageManager packageManager, String packageName, @Nullable String activityClassName) {
+ if (activityClassName == null) {
return "";
}
- CharSequence activityName = accessibilityEvent.getClassName();
- if (accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- && accessibilityEvent.getPackageName() != null
- && activityName != null) {
- try {
- // Check class is for a valid Activity.
- packageManager
- .getActivityInfo(
- new ComponentName(accessibilityEvent.getPackageName().toString(),
- activityName.toString()), 0);
- int qualifierEnd = activityName.toString().lastIndexOf('.');
- return activityName.toString().substring(qualifierEnd + 1);
- } catch (PackageManager.NameNotFoundException e) {
- // No need to spam the logs. This is very frequent when the class doesn't match
- // an activity.
- }
+ try {
+ // Check class is for a valid Activity.
+ packageManager.getActivityInfo(new ComponentName(packageName, activityClassName), 0);
+ int qualifierEnd = activityClassName.lastIndexOf('.');
+ return activityClassName.substring(qualifierEnd + 1);
+ } catch (PackageManager.NameNotFoundException e) {
+ // No need to spam the logs. This is very frequent when the class doesn't match
+ // an activity.
}
return "";
}
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/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index ced7773..11de258 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -130,17 +130,18 @@
}
@Override
- public void sendMessage(int subId, String callingPkg, Uri contentUri, String locationUrl,
- Bundle configOverrides, PendingIntent sentIntent, long messageId,
+ public void sendMessage(int subId, int callingUser, String callingPkg,
+ Uri contentUri, String locationUrl, Bundle configOverrides,
+ PendingIntent sentIntent, long messageId,
String attributionTag) throws RemoteException {
returnPendingIntentWithError(sentIntent);
}
@Override
- public void downloadMessage(int subId, String callingPkg, String locationUrl,
- Uri contentUri, Bundle configOverrides, PendingIntent downloadedIntent,
- long messageId, String attributionTag)
- throws RemoteException {
+ public void downloadMessage(int subId, int callingUser, String callingPkg,
+ String locationUrl, Uri contentUri, Bundle configOverrides,
+ PendingIntent downloadedIntent,
+ long messageId, String attributionTag) throws RemoteException {
returnPendingIntentWithError(downloadedIntent);
}
@@ -151,8 +152,9 @@
}
@Override
- public Uri importMultimediaMessage(String callingPkg, Uri contentUri, String messageId,
- long timestampSecs, boolean seen, boolean read) throws RemoteException {
+ public Uri importMultimediaMessage(int callingUser, String callingPkg,
+ Uri contentUri, String messageId, long timestampSecs,
+ boolean seen, boolean read) throws RemoteException {
return null;
}
@@ -187,8 +189,8 @@
}
@Override
- public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri)
- throws RemoteException {
+ public Uri addMultimediaMessageDraft(int callingUser, String callingPkg,
+ Uri contentUri) throws RemoteException {
return null;
}
@@ -333,9 +335,9 @@
private static final String PHONE_PACKAGE_NAME = "com.android.phone";
@Override
- public void sendMessage(int subId, String callingPkg, Uri contentUri,
- String locationUrl, Bundle configOverrides, PendingIntent sentIntent,
- long messageId, String attributionTag)
+ public void sendMessage(int subId, int callingUser, String callingPkg,
+ Uri contentUri, String locationUrl, Bundle configOverrides,
+ PendingIntent sentIntent, long messageId, String attributionTag)
throws RemoteException {
Slog.d(TAG, "sendMessage() by " + callingPkg);
mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send MMS message");
@@ -360,14 +362,15 @@
CarrierMessagingService.SERVICE_INTERFACE,
Intent.FLAG_GRANT_READ_URI_PERMISSION,
subId);
- getServiceGuarded().sendMessage(subId, callingPkg, contentUri, locationUrl,
- configOverrides, sentIntent, messageId, attributionTag);
+ getServiceGuarded().sendMessage(subId, getCallingUserId(), callingPkg, contentUri,
+ locationUrl, configOverrides, sentIntent, messageId, attributionTag);
}
@Override
- public void downloadMessage(int subId, String callingPkg, String locationUrl,
- Uri contentUri, Bundle configOverrides, PendingIntent downloadedIntent,
- long messageId, String attributionTag) throws RemoteException {
+ public void downloadMessage(int subId, int callingUser, String callingPkg,
+ String locationUrl, Uri contentUri, Bundle configOverrides,
+ PendingIntent downloadedIntent, long messageId, String attributionTag)
+ throws RemoteException {
Slog.d(TAG, "downloadMessage() by " + callingPkg);
mContext.enforceCallingPermission(Manifest.permission.RECEIVE_MMS,
"Download MMS message");
@@ -381,8 +384,8 @@
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
subId);
- getServiceGuarded().downloadMessage(subId, callingPkg, locationUrl, contentUri,
- configOverrides, downloadedIntent, messageId, attributionTag);
+ getServiceGuarded().downloadMessage(subId, getCallingUserId(), callingPkg, locationUrl,
+ contentUri, configOverrides, downloadedIntent, messageId, attributionTag);
}
@Override
@@ -399,8 +402,8 @@
}
@Override
- public Uri importMultimediaMessage(String callingPkg, Uri contentUri,
- String messageId, long timestampSecs, boolean seen, boolean read)
+ public Uri importMultimediaMessage(int callingUser, String callingPkg,
+ Uri contentUri, String messageId, long timestampSecs, boolean seen, boolean read)
throws RemoteException {
if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
callingPkg, null, null) != AppOpsManager.MODE_ALLOWED) {
@@ -408,8 +411,8 @@
// while writing the TelephonyProvider
return FAKE_MMS_SENT_URI;
}
- return getServiceGuarded().importMultimediaMessage(
- callingPkg, contentUri, messageId, timestampSecs, seen, read);
+ return getServiceGuarded().importMultimediaMessage(getCallingUserId(), callingPkg,
+ contentUri, messageId, timestampSecs, seen, read);
}
@Override
@@ -467,15 +470,16 @@
}
@Override
- public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri)
- throws RemoteException {
+ public Uri addMultimediaMessageDraft(int callingUser, String callingPkg,
+ Uri contentUri) throws RemoteException {
if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
callingPkg, null, null) != AppOpsManager.MODE_ALLOWED) {
// Silently fail AppOps failure due to not being the default SMS app
// while writing the TelephonyProvider
return FAKE_MMS_DRAFT_URI;
}
- return getServiceGuarded().addMultimediaMessageDraft(callingPkg, contentUri);
+ return getServiceGuarded().addMultimediaMessageDraft(getCallingUserId(), callingPkg,
+ contentUri);
}
@Override
@@ -572,4 +576,13 @@
if (info == null) return INVALID_SIM_SLOT_INDEX;
return info.getSimSlotIndex();
}
+
+ /**
+ * Retrieves the calling user id.
+ * @return The id of the calling user.
+ */
+ private int getCallingUserId() {
+ return Binder.getCallingUserHandle().getIdentifier();
+ }
+
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 3633d0f..33cf842 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -55,6 +55,7 @@
import android.telephony.BarringInfo;
import android.telephony.CallQuality;
import android.telephony.CallState;
+import android.telephony.CarrierConfigManager;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.CellSignalStrength;
@@ -426,6 +427,7 @@
private boolean[] mSCBMStarted;
private boolean[] mCarrierRoamingNtnMode = null;
+ private boolean[] mCarrierRoamingNtnEligible = null;
/**
* Per-phone map of precise data connection state. The key of the map is the pair of transport
@@ -726,6 +728,7 @@
mSCBMReason = copyOf(mSCBMReason, mNumPhones);
mSCBMStarted = copyOf(mSCBMStarted, mNumPhones);
mCarrierRoamingNtnMode = copyOf(mCarrierRoamingNtnMode, mNumPhones);
+ mCarrierRoamingNtnEligible = copyOf(mCarrierRoamingNtnEligible, mNumPhones);
// ds -> ss switch.
if (mNumPhones < oldNumPhones) {
cutListToSize(mCellInfo, mNumPhones);
@@ -785,6 +788,7 @@
mSCBMReason[i] = TelephonyManager.STOP_REASON_UNKNOWN;
mSCBMStarted[i] = false;
mCarrierRoamingNtnMode[i] = false;
+ mCarrierRoamingNtnEligible[i] = false;
}
}
}
@@ -859,6 +863,7 @@
mSCBMReason = new int[numPhones];
mSCBMStarted = new boolean[numPhones];
mCarrierRoamingNtnMode = new boolean[numPhones];
+ mCarrierRoamingNtnEligible = new boolean[numPhones];
for (int i = 0; i < numPhones; i++) {
mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
@@ -903,6 +908,7 @@
mSCBMReason[i] = TelephonyManager.STOP_REASON_UNKNOWN;
mSCBMStarted[i] = false;
mCarrierRoamingNtnMode[i] = false;
+ mCarrierRoamingNtnEligible[i] = false;
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -1518,6 +1524,15 @@
remove(r.binder);
}
}
+ if (events.contains(
+ TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED)) {
+ try {
+ r.callback.onCarrierRoamingNtnEligibleStateChanged(
+ mCarrierRoamingNtnEligible[r.phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
}
}
}
@@ -3536,6 +3551,53 @@
}
}
+ /**
+ * Notify external listeners that device eligibility to connect to carrier roaming
+ * non-terrestrial network changed.
+ *
+ * @param subId subscription ID.
+ * @param eligible {@code true} when the device is eligible for satellite
+ * communication if all the following conditions are met:
+ * <ul>
+ * <li>Any subscription on the device supports P2P satellite messaging which is defined by
+ * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li>
+ * <li>{@link CarrierConfigManager#KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT} set to
+ * {@link CarrierConfigManager#CARRIER_ROAMING_NTN_CONNECT_MANUAL} </li>
+ * <li>The device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi,
+ * and the hysteresis timer defined by {@link CarrierConfigManager
+ * #KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT} is expired. </li>
+ * </ul>
+ */
+ public void notifyCarrierRoamingNtnEligibleStateChanged(int subId, boolean eligible) {
+ if (!checkNotifyPermission("notifyCarrierRoamingNtnEligibleStateChanged")) {
+ log("notifyCarrierRoamingNtnEligibleStateChanged: caller does not have required "
+ + "permissions.");
+ return;
+ }
+
+ if (VDBG) {
+ log("notifyCarrierRoamingNtnEligibleStateChanged: "
+ + "subId=" + subId + " eligible" + eligible);
+ }
+
+ synchronized (mRecords) {
+ int phoneId = getPhoneIdFromSubId(subId);
+ mCarrierRoamingNtnEligible[phoneId] = eligible;
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ r.callback.onCarrierRoamingNtnEligibleStateChanged(eligible);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
@NeverCompile // Avoid size overhead of debugging code.
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -3589,6 +3651,8 @@
pw.println("mECBMStarted=" + mECBMStarted[i]);
pw.println("mSCBMReason=" + mSCBMReason[i]);
pw.println("mSCBMStarted=" + mSCBMStarted[i]);
+ pw.println("mCarrierRoamingNtnMode=" + mCarrierRoamingNtnMode[i]);
+ pw.println("mCarrierRoamingNtnEligible=" + mCarrierRoamingNtnEligible[i]);
// We need to obfuscate package names, and primitive arrays' native toString is ugly
Pair<List<String>, int[]> carrierPrivilegeState = mCarrierPrivilegeStates.get(i);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index f821e00..69478bb 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4616,6 +4616,9 @@
opPackageName,
visibleAccountTypes,
includeUserManagedNotVisible);
+ } catch (SQLiteException e) {
+ Log.w(TAG, "Could not get accounts for user " + userId, e);
+ return new Account[]{};
} finally {
restoreCallingIdentity(identityToken);
}
@@ -4703,12 +4706,17 @@
public Account[] getSharedAccountsAsUser(int userId) {
userId = handleIncomingUser(userId);
- UserAccounts accounts = getUserAccounts(userId);
- synchronized (accounts.dbLock) {
- List<Account> accountList = accounts.accountsDb.getSharedAccounts();
- Account[] accountArray = new Account[accountList.size()];
- accountList.toArray(accountArray);
- return accountArray;
+ try {
+ UserAccounts accounts = getUserAccounts(userId);
+ synchronized (accounts.dbLock) {
+ List<Account> accountList = accounts.accountsDb.getSharedAccounts();
+ Account[] accountArray = new Account[accountList.size()];
+ accountList.toArray(accountArray);
+ return accountArray;
+ }
+ } catch (SQLiteException e) {
+ Log.w(TAG, "Could not get shared accounts for user " + userId, e);
+ return new Account[]{};
}
}
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 47b65eb..1f88657 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -353,8 +353,8 @@
}
/** Called when there is a low memory kill */
- void scheduleNoteLmkdProcKilled(final int pid, final int uid) {
- mKillHandler.obtainMessage(KillHandler.MSG_LMKD_PROC_KILLED, pid, uid)
+ void scheduleNoteLmkdProcKilled(final int pid, final int uid, final int rssKb) {
+ mKillHandler.obtainMessage(KillHandler.MSG_LMKD_PROC_KILLED, pid, uid, Long.valueOf(rssKb))
.sendToTarget();
}
@@ -401,9 +401,9 @@
if (lmkd != null) {
updateExistingExitInfoRecordLocked(info, null,
- ApplicationExitInfo.REASON_LOW_MEMORY);
+ ApplicationExitInfo.REASON_LOW_MEMORY, (Long) lmkd.second);
} else if (zygote != null) {
- updateExistingExitInfoRecordLocked(info, (Integer) zygote.second, null);
+ updateExistingExitInfoRecordLocked(info, (Integer) zygote.second, null, null);
} else {
scheduleLogToStatsdLocked(info, false);
}
@@ -486,7 +486,7 @@
*/
@GuardedBy("mLock")
private void updateExistingExitInfoRecordLocked(ApplicationExitInfo info,
- Integer status, Integer reason) {
+ Integer status, Integer reason, Long rssKb) {
if (info == null || !isFresh(info.getTimestamp())) {
// if the record is way outdated, don't update it then (because of potential pid reuse)
return;
@@ -513,6 +513,9 @@
immediateLog = true;
}
}
+ if (rssKb != null) {
+ info.setRss(rssKb.longValue());
+ }
scheduleLogToStatsdLocked(info, immediateLog);
}
@@ -523,7 +526,7 @@
*/
@GuardedBy("mLock")
private boolean updateExitInfoIfNecessaryLocked(
- int pid, int uid, Integer status, Integer reason) {
+ int pid, int uid, Integer status, Integer reason, Long rssKb) {
Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
if (k != null) {
uid = k;
@@ -552,7 +555,7 @@
// always be the first one we se as `getExitInfosLocked()` returns them sorted
// by most-recent-first.
isModified[0] = true;
- updateExistingExitInfoRecordLocked(info, status, reason);
+ updateExistingExitInfoRecordLocked(info, status, reason, rssKb);
return FOREACH_ACTION_STOP_ITERATION;
}
return FOREACH_ACTION_NONE;
@@ -1668,11 +1671,11 @@
switch (msg.what) {
case MSG_LMKD_PROC_KILLED:
mAppExitInfoSourceLmkd.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
- null /* status */);
+ null /* status */, (Long) msg.obj /* rss_kb */);
break;
case MSG_CHILD_PROC_DIED:
mAppExitInfoSourceZygote.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
- (Integer) msg.obj /* status */);
+ (Integer) msg.obj /* status */, null /* rss_kb */);
break;
case MSG_PROC_DIED: {
ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
@@ -1833,7 +1836,7 @@
}
}
- void onProcDied(final int pid, final int uid, final Integer status) {
+ void onProcDied(final int pid, final int uid, final Integer status, final Long rssKb) {
if (DEBUG_PROCESSES) {
Slog.i(TAG, mTag + ": proc died: pid=" + pid + " uid=" + uid
+ ", status=" + status);
@@ -1846,8 +1849,12 @@
// Unlikely but possible: the record has been created
// Let's update it if we could find a ApplicationExitInfo record
synchronized (mLock) {
- if (!updateExitInfoIfNecessaryLocked(pid, uid, status, mPresetReason)) {
- addLocked(pid, uid, status);
+ if (!updateExitInfoIfNecessaryLocked(pid, uid, status, mPresetReason, rssKb)) {
+ if (rssKb != null) {
+ addLocked(pid, uid, rssKb); // lmkd
+ } else {
+ addLocked(pid, uid, status); // zygote
+ }
}
// Notify any interesed party regarding the lmkd kills
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 8df4e77..e46ab8f 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -124,6 +124,7 @@
import com.android.server.pm.UserManagerInternal;
import com.android.server.power.optimization.Flags;
import com.android.server.power.stats.AggregatedPowerStatsConfig;
+import com.android.server.power.stats.AmbientDisplayPowerStatsProcessor;
import com.android.server.power.stats.AudioPowerStatsProcessor;
import com.android.server.power.stats.BatteryExternalStatsWorker;
import com.android.server.power.stats.BatteryStatsDumpHelperImpl;
@@ -142,6 +143,7 @@
import com.android.server.power.stats.PowerStatsScheduler;
import com.android.server.power.stats.PowerStatsStore;
import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.ScreenPowerStatsProcessor;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.server.power.stats.VideoPowerStatsProcessor;
import com.android.server.power.stats.WifiPowerStatsProcessor;
@@ -488,6 +490,20 @@
.setProcessor(
new CpuPowerStatsProcessor(mPowerProfile, mCpuScalingPolicies));
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SCREEN)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .setProcessor(
+ new ScreenPowerStatsProcessor(mPowerProfile));
+
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
+ BatteryConsumer.POWER_COMPONENT_SCREEN)
+ .setProcessor(new AmbientDisplayPowerStatsProcessor());
+
config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
.trackDeviceStates(
AggregatedPowerStatsConfig.STATE_POWER,
@@ -636,6 +652,18 @@
BatteryConsumer.POWER_COMPONENT_CPU,
Flags.streamlinedBatteryStats());
+ mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_SCREEN,
+ Flags.streamlinedMiscBatteryStats());
+ mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+ BatteryConsumer.POWER_COMPONENT_SCREEN,
+ Flags.streamlinedMiscBatteryStats());
+
+ mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
+ Flags.streamlinedMiscBatteryStats());
+ mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+ BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
+ Flags.streamlinedMiscBatteryStats());
+
mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
Flags.streamlinedConnectivityBatteryStats());
mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 91b64f8..6bb56c9 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -25,7 +25,6 @@
import android.os.Bundle;
import android.provider.DeviceConfig;
import android.provider.Settings;
-import android.text.TextFlags;
import android.widget.WidgetFlags;
import com.android.internal.R;
@@ -163,22 +162,6 @@
WidgetFlags.KEY_MAGNIFIER_ASPECT_RATIO, float.class,
WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT));
- sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
- TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU,
- TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class,
- TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
-
- // Register all text aconfig flags.
- for (int i = 0; i < TextFlags.TEXT_ACONFIGS_FLAGS.length; i++) {
- final String flag = TextFlags.TEXT_ACONFIGS_FLAGS[i];
- final boolean defaultValue = TextFlags.TEXT_ACONFIG_DEFAULT_VALUE[i];
- sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
- TextFlags.NAMESPACE,
- flag,
- TextFlags.getKeyForFlag(flag),
- boolean.class,
- defaultValue));
- }
// add other device configs here...
}
private static volatile boolean sDeviceConfigContextEntriesLoaded = false;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 779aabe..726e827 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -951,12 +951,14 @@
try {
switch (inputData.readInt()) {
case LMK_PROCKILL:
- if (receivedLen != 12) {
+ if (receivedLen != 16) {
return false;
}
final int pid = inputData.readInt();
final int uid = inputData.readInt();
- mAppExitInfoTracker.scheduleNoteLmkdProcKilled(pid, uid);
+ final int rssKb = inputData.readInt();
+ mAppExitInfoTracker.scheduleNoteLmkdProcKilled(pid, uid,
+ rssKb);
return true;
case LMK_KILL_OCCURRED:
if (receivedLen
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 7a24e9df..1183768 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3692,6 +3692,11 @@
ensureValidStreamType(streamType);
final int resolvedStream = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
+ if (resolvedStream == -1) {
+ Log.e(TAG, "adjustSuggestedStreamVolume: no stream vol alias for stream type "
+ + streamType);
+ return;
+ }
// Play sounds on STREAM_RING only.
if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
@@ -3809,6 +3814,10 @@
// including with regard to silent mode control (e.g the use of STREAM_RING below and in
// checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION)
int streamTypeAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
+ if (streamTypeAlias == -1) {
+ Log.e(TAG,
+ "adjustStreamVolume: no stream vol alias for stream type " + streamType);
+ }
VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
@@ -4077,8 +4086,8 @@
synchronized (mSettingsLock) {
synchronized (VolumeStreamState.class) {
List<Integer> streamsToMute = new ArrayList<>();
- for (int stream = 0; stream < mStreamStates.size(); stream++) {
- final VolumeStreamState vss = mStreamStates.valueAt(stream);
+ for (int streamIdx = 0; streamIdx < mStreamStates.size(); streamIdx++) {
+ final VolumeStreamState vss = mStreamStates.valueAt(streamIdx);
if (vss != null && streamAlias == sStreamVolumeAlias.get(vss.getStreamType())
&& vss.isMutable()) {
if (!(mCameraSoundForced && (vss.getStreamType()
@@ -4219,6 +4228,10 @@
/*package*/ void onSetStreamVolume(int streamType, int index, int flags, int device,
String caller, boolean hasModifyAudioSettings, boolean canChangeMute) {
final int stream = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
+ if (stream == -1) {
+ Log.e(TAG, "onSetStreamVolume: no stream vol alias for stream type " + stream);
+ return;
+ }
// setting volume on ui sounds stream type also controls silent mode
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(stream == getUiSoundsStreamType())) {
@@ -4868,6 +4881,10 @@
ensureValidStreamType(streamType);
int streamTypeAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound*/-1);
+ if (streamTypeAlias == -1) {
+ Log.e(TAG, "setStreamVolume: no stream vol alias for stream type " + streamType);
+ return;
+ }
final VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
if (!replaceStreamBtSco() && (streamType == AudioManager.STREAM_VOICE_CALL)
@@ -5586,9 +5603,9 @@
return new ArrayList<>(Arrays.stream(AudioManager.getPublicStreamTypes())
.boxed().toList());
}
- ArrayList<Integer> res = new ArrayList(1);
- for (int stream = 0; stream < sStreamVolumeAlias.size(); ++stream) {
- final int streamAlias = sStreamVolumeAlias.valueAt(stream);
+ ArrayList<Integer> res = new ArrayList<>(1);
+ for (int streamIdx = 0; streamIdx < sStreamVolumeAlias.size(); ++streamIdx) {
+ final int streamAlias = sStreamVolumeAlias.valueAt(streamIdx);
if (!res.contains(streamAlias)) {
res.add(streamAlias);
}
@@ -6408,6 +6425,10 @@
final int device = getDeviceForStream(streamType);
final int streamAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/
-1);
+ if (streamAlias == -1) {
+ Log.e(TAG,
+ "onUpdateAudioMode: no stream vol alias for stream type " + streamType);
+ }
if (DEBUG_MODE) {
Log.v(TAG, "onUpdateAudioMode: streamType=" + streamType
@@ -7614,8 +7635,8 @@
? MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM]
: Math.min(idx + 1, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
// update the VolumeStreamState for STREAM_ALARM and its aliases
- for (int stream = 0; stream < sStreamVolumeAlias.size(); ++stream) {
- final int streamAlias = sStreamVolumeAlias.valueAt(stream);
+ for (int streamIdx = 0; streamIdx < sStreamVolumeAlias.size(); ++streamIdx) {
+ final int streamAlias = sStreamVolumeAlias.valueAt(streamIdx);
if (streamAlias == AudioSystem.STREAM_ALARM) {
getVssForStreamOrDefault(streamAlias).updateNoPermMinIndex(safeIndex);
}
@@ -9454,7 +9475,7 @@
// must be sync'd on mSettingsLock before VolumeStreamState.class
@GuardedBy("VolumeStreamState.class")
public void setAllIndexes(VolumeStreamState srcStream, String caller) {
- if (mStreamType == srcStream.mStreamType) {
+ if (srcStream == null || mStreamType == srcStream.mStreamType) {
return;
}
int srcStreamType = srcStream.getStreamType();
diff --git a/services/core/java/com/android/server/crashrecovery/TEST_MAPPING b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
index 615db34..537fb325 100644
--- a/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
+++ b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
@@ -1,4 +1,9 @@
{
+ "presubmit": [
+ {
+ "name": "CrashRecoveryModuleTests"
+ }
+ ],
"postsubmit": [
{
"name": "FrameworksMockingServicesTests",
@@ -7,9 +12,6 @@
"include-filter": "com.android.server.RescuePartyTest"
}
]
- },
- {
- "name": "CrashRecoveryModuleTests"
}
]
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1177be2..8b21d98 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1755,6 +1755,7 @@
mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
mTempBrightnessEvent.setPhysicalDisplayName(mPhysicalDisplayName);
mTempBrightnessEvent.setDisplayState(state);
+ mTempBrightnessEvent.setDisplayStateReason(stateAndReason.second);
mTempBrightnessEvent.setDisplayPolicy(mPowerRequest.policy);
mTempBrightnessEvent.setReason(mBrightnessReason);
mTempBrightnessEvent.setHbmMax(hbmMax);
diff --git a/services/core/java/com/android/server/display/ExternalDisplayStatsService.java b/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
index f6f23d9..608fb35 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
@@ -518,18 +518,24 @@
private void logExternalDisplayIdleStarted() {
synchronized (mExternalDisplayStates) {
for (var i = 0; i < mExternalDisplayStates.size(); i++) {
- mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
- KEYGUARD, i + 1, mIsExternalDisplayUsedForAudio);
- if (DEBUG) {
- final int displayId = mExternalDisplayStates.keyAt(i);
- final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
- Slog.d(TAG, "logExternalDisplayIdleStarted"
- + " displayId=" + displayId
- + " currentState=" + state
- + " countOfExternalDisplays=" + (i + 1)
- + " state=" + KEYGUARD
- + " mIsExternalDisplayUsedForAudio="
- + mIsExternalDisplayUsedForAudio);
+ final int displayId = mExternalDisplayStates.keyAt(i);
+ final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
+ // Don't try to stop "connected" session by keyguard event.
+ // There is no purpose to measure how long keyguard is shown while external
+ // display is connected but not used for mirroring or extended display.
+ // Therefore there no need to log this event.
+ if (state != DISCONNECTED_STATE && state != CONNECTED_STATE) {
+ mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
+ KEYGUARD, i + 1, mIsExternalDisplayUsedForAudio);
+ if (DEBUG) {
+ Slog.d(TAG, "logExternalDisplayIdleStarted"
+ + " displayId=" + displayId
+ + " currentState=" + state
+ + " countOfExternalDisplays=" + (i + 1)
+ + " state=" + KEYGUARD
+ + " mIsExternalDisplayUsedForAudio="
+ + mIsExternalDisplayUsedForAudio);
+ }
}
}
}
@@ -540,7 +546,11 @@
for (var i = 0; i < mExternalDisplayStates.size(); i++) {
final int displayId = mExternalDisplayStates.keyAt(i);
final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
- if (state == DISCONNECTED_STATE) {
+ // No need to restart "connected" session after keyguard is stopped.
+ // This is because the connection is continuous even if keyguard is shown.
+ // In case in the future keyguard needs to be measured also while display
+ // is not used, then a 'keyguard finished' event needs to be emitted in this case.
+ if (state == DISCONNECTED_STATE || state == CONNECTED_STATE) {
return;
}
mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index 5cc603c..ad57ebf 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -52,6 +52,8 @@
private String mPhysicalDisplayId;
private String mPhysicalDisplayName;
private int mDisplayState;
+ @Display.StateReason
+ private int mDisplayStateReason;
private int mDisplayPolicy;
private long mTime;
private float mLux;
@@ -96,6 +98,7 @@
mPhysicalDisplayId = that.getPhysicalDisplayId();
mPhysicalDisplayName = that.getPhysicalDisplayName();
mDisplayState = that.mDisplayState;
+ mDisplayStateReason = that.mDisplayStateReason;
mDisplayPolicy = that.mDisplayPolicy;
mTime = that.getTime();
// Lux values
@@ -133,6 +136,7 @@
mPhysicalDisplayId = "";
mPhysicalDisplayName = "";
mDisplayState = Display.STATE_UNKNOWN;
+ mDisplayStateReason = Display.STATE_REASON_UNKNOWN;
mDisplayPolicy = POLICY_OFF;
// Lux values
mLux = INVALID_LUX;
@@ -176,6 +180,7 @@
&& mPhysicalDisplayId.equals(that.mPhysicalDisplayId)
&& mPhysicalDisplayName.equals(that.mPhysicalDisplayName)
&& mDisplayState == that.mDisplayState
+ && mDisplayStateReason == that.mDisplayStateReason
&& mDisplayPolicy == that.mDisplayPolicy
&& Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
&& Float.floatToRawIntBits(mPreThresholdLux)
@@ -221,6 +226,7 @@
+ ", reason=" + mReason.toString(mAdjustmentFlags)
+ ", strat=" + mDisplayBrightnessStrategyName
+ ", state=" + Display.stateToString(mDisplayState)
+ + ", stateReason=" + Display.stateReasonToString(mDisplayStateReason)
+ ", policy=" + policyToString(mDisplayPolicy)
+ ", flags=" + flagsToString()
// Autobrightness
@@ -293,6 +299,10 @@
mDisplayState = state;
}
+ public void setDisplayStateReason(@Display.StateReason int reason) {
+ mDisplayStateReason = reason;
+ }
+
public void setDisplayPolicy(int policy) {
mDisplayPolicy = policy;
}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 886857c..d43e783 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -544,11 +544,10 @@
private void startDozingInternal(IBinder token, int screenState,
@Display.StateReason int reason, int screenBrightness) {
- if (DEBUG) {
- Slog.d(TAG, "Dream requested to start dozing: " + token
- + ", screenState=" + screenState
- + ", screenBrightness=" + screenBrightness);
- }
+ Slog.d(TAG, "Dream requested to start dozing: " + token
+ + ", screenState=" + Display.stateToString(screenState)
+ + ", reason=" + Display.stateReasonToString(reason)
+ + ", screenBrightness=" + screenBrightness);
synchronized (mLock) {
if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.canDoze) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index bb2efa1..55de9aa 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1259,9 +1259,9 @@
* @param dragAndDropChannel The input channel associated with the system drag window.
* @return true if drag and drop was successfully started, false otherwise.
*/
- public boolean startDragAndDrop(@NonNull InputChannel fromChannel,
- @NonNull InputChannel dragAndDropChannel) {
- return mNative.transferTouchGesture(fromChannel.getToken(), dragAndDropChannel.getToken(),
+ public boolean startDragAndDrop(@NonNull IBinder fromChannelToken,
+ @NonNull IBinder dragAndDropChannelToken) {
+ return mNative.transferTouchGesture(fromChannelToken, dragAndDropChannelToken,
true /* isDragDrop */);
}
@@ -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/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 4993412..1d1a178 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -22,6 +22,8 @@
import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
+import static com.android.hardware.input.Flags.keyboardLayoutManagerMultiUserImeSetup;
+
import android.annotation.AnyThread;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -1066,9 +1068,15 @@
for (InputMethodInfo imeInfo :
inputMethodManagerInternal.getEnabledInputMethodListAsUser(
userId)) {
- for (InputMethodSubtype imeSubtype :
- inputMethodManager.getEnabledInputMethodSubtypeList(
- imeInfo, true /* allowsImplicitlyEnabledSubtypes */)) {
+ final List<InputMethodSubtype> imeSubtypes;
+ if (keyboardLayoutManagerMultiUserImeSetup()) {
+ imeSubtypes = inputMethodManagerInternal.getEnabledInputMethodSubtypeListAsUser(
+ imeInfo.getId(), true /* allowsImplicitlyEnabledSubtypes */, userId);
+ } else {
+ imeSubtypes = inputMethodManager.getEnabledInputMethodSubtypeList(imeInfo,
+ true /* allowsImplicitlyEnabledSubtypes */);
+ }
+ for (InputMethodSubtype imeSubtype : imeSubtypes) {
if (!imeSubtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
continue;
}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index 99f4747..b08f917 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -38,10 +38,11 @@
final class AdditionalSubtypeMapRepository {
private static final String TAG = "AdditionalSubtypeMapRepository";
- // TODO(b/352594784): Should we user other lock primitives?
- @GuardedBy("sPerUserMap")
+ private static final Object sMutationLock = new Object();
+
@NonNull
- private static final SparseArray<AdditionalSubtypeMap> sPerUserMap = new SparseArray<>();
+ private static volatile ImmutableSparseArray<AdditionalSubtypeMap> sPerUserMap =
+ ImmutableSparseArray.empty();
record WriteTask(@UserIdInt int userId, @NonNull AdditionalSubtypeMap subtypeMap,
@NonNull InputMethodMap inputMethodMap) {
@@ -198,7 +199,7 @@
/**
* Returns {@link AdditionalSubtypeMap} for the given user.
*
- * <p>This method is expected be called after {@link #ensureInitializedAndGet(int)}. Otherwise
+ * <p>This method is expected be called after {@link #initializeIfNecessary(int)}. Otherwise
* {@link AdditionalSubtypeMap#EMPTY_MAP} will be returned.</p>
*
* @param userId the user to be queried about
@@ -207,10 +208,7 @@
@AnyThread
@NonNull
static AdditionalSubtypeMap get(@UserIdInt int userId) {
- final AdditionalSubtypeMap map;
- synchronized (sPerUserMap) {
- map = sPerUserMap.get(userId);
- }
+ final AdditionalSubtypeMap map = sPerUserMap.get(userId);
if (map == null) {
Slog.e(TAG, "get(userId=" + userId + ") is called before loadInitialDataAndGet()."
+ " Returning an empty map");
@@ -220,28 +218,24 @@
}
/**
- * Ensures that {@link AdditionalSubtypeMap} is initialized for the given user. Load it from
- * the persistent storage if {@link #putAndSave(int, AdditionalSubtypeMap, InputMethodMap)} has
- * not been called yet.
+ * Ensures that {@link AdditionalSubtypeMap} is initialized for the given user.
*
* @param userId the user to be initialized
- * @return {@link AdditionalSubtypeMap} that is associated with the given user. If
- * {@link #putAndSave(int, AdditionalSubtypeMap, InputMethodMap)} is already called
- * then the given {@link AdditionalSubtypeMap}.
*/
@AnyThread
@NonNull
- static AdditionalSubtypeMap ensureInitializedAndGet(@UserIdInt int userId) {
- final var map = AdditionalSubtypeUtils.load(userId);
- synchronized (sPerUserMap) {
- final AdditionalSubtypeMap previous = sPerUserMap.get(userId);
- // If putAndSave() has already been called, then use it.
- if (previous != null) {
- return previous;
- }
- sPerUserMap.put(userId, map);
+ static void initializeIfNecessary(@UserIdInt int userId) {
+ if (sPerUserMap.contains(userId)) {
+ // Fast-pass. If putAndSave() is already called, then do nothing.
+ return;
}
- return map;
+ final var map = AdditionalSubtypeUtils.load(userId);
+ synchronized (sMutationLock) {
+ // Check the condition again.
+ if (!sPerUserMap.contains(userId)) {
+ sPerUserMap = sPerUserMap.cloneWithPutOrSelf(userId, map);
+ }
+ }
}
/**
@@ -255,12 +249,8 @@
@AnyThread
static void putAndSave(@UserIdInt int userId, @NonNull AdditionalSubtypeMap map,
@NonNull InputMethodMap inputMethodMap) {
- synchronized (sPerUserMap) {
- final AdditionalSubtypeMap previous = sPerUserMap.get(userId);
- if (previous == map) {
- return;
- }
- sPerUserMap.put(userId, map);
+ synchronized (sMutationLock) {
+ sPerUserMap = sPerUserMap.cloneWithPutOrSelf(userId, map);
sWriter.scheduleWriteTask(userId, map, inputMethodMap);
}
}
@@ -277,9 +267,9 @@
@AnyThread
static void remove(@UserIdInt int userId) {
- synchronized (sPerUserMap) {
+ synchronized (sMutationLock) {
sWriter.onUserRemoved(userId);
- sPerUserMap.remove(userId);
+ sPerUserMap = sPerUserMap.cloneWithRemoveOrSelf(userId);
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 7ebf595..ae41133 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -36,6 +36,7 @@
import static com.android.server.inputmethod.InputMethodManagerService.computeImeDisplayIdForTarget;
import android.accessibilityservice.AccessibilityService;
+import android.annotation.AnyThread;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
@@ -83,6 +84,7 @@
* A map used to track the requested IME target window and its state. The key represents the
* token of the window and the value is the corresponding IME window state.
*/
+ @GuardedBy("ImfLock.class")
private final WeakHashMap<IBinder, ImeTargetWindowState> mRequestWindowStateMap =
new WeakHashMap<>();
@@ -93,6 +95,7 @@
* @see InputMethodManager#HIDE_IMPLICIT_ONLY that system will not hide IME when the value is
* {@code true}.
*/
+ @GuardedBy("ImfLock.class")
boolean mRequestedShowExplicitly;
/**
@@ -101,23 +104,28 @@
* @see InputMethodManager#SHOW_FORCED
* @see InputMethodManager#HIDE_NOT_ALWAYS
*/
+ @GuardedBy("ImfLock.class")
boolean mShowForced;
/**
* Set if we last told the input method to show itself.
*/
+ @GuardedBy("ImfLock.class")
private boolean mInputShown;
/**
* Set if we called
* {@link com.android.server.wm.ImeTargetVisibilityPolicy#showImeScreenshot(IBinder, int)}.
*/
+ @GuardedBy("ImfLock.class")
private boolean mRequestedImeScreenshot;
/** The window token of the current visible IME layering target overlay. */
+ @GuardedBy("ImfLock.class")
private IBinder mCurVisibleImeLayeringOverlay;
/** The window token of the current visible IME input target. */
+ @GuardedBy("ImfLock.class")
private IBinder mCurVisibleImeInputTarget;
/** Represent the invalid IME visibility state */
@@ -203,25 +211,32 @@
public void onImeTargetOverlayVisibilityChanged(IBinder overlayWindowToken,
@WindowManager.LayoutParams.WindowType int windowType, boolean visible,
boolean removed) {
- mCurVisibleImeLayeringOverlay =
- // Ignoring the starting window since it's ok to cover the IME target
- // window in temporary without affecting the IME visibility.
- (visible && !removed && windowType != TYPE_APPLICATION_STARTING)
+ // Ignoring the starting window since it's ok to cover the IME target
+ // window in temporary without affecting the IME visibility.
+ final var overlay = (visible && !removed && windowType != TYPE_APPLICATION_STARTING)
? overlayWindowToken : null;
+ synchronized (ImfLock.class) {
+ mCurVisibleImeLayeringOverlay = overlay;
+
+ }
}
@Override
public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget,
boolean visibleRequested, boolean removed) {
- if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested || removed)
- && mCurVisibleImeLayeringOverlay != null) {
- final int reason = SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE;
- final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
- ImeTracker.ORIGIN_SERVER, reason, false /* fromUser */);
- mService.onApplyImeVisibilityFromComputer(imeInputTarget, statsToken,
- new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, reason));
+ synchronized (ImfLock.class) {
+ if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested
+ || removed)
+ && mCurVisibleImeLayeringOverlay != null) {
+ final int reason = SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE;
+ final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+ ImeTracker.ORIGIN_SERVER, reason, false /* fromUser */);
+ mService.onApplyImeVisibilityFromComputerLocked(imeInputTarget, statsToken,
+ new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, reason));
+ }
+ mCurVisibleImeInputTarget =
+ (visibleRequested && !removed) ? imeInputTarget : null;
}
- mCurVisibleImeInputTarget = (visibleRequested && !removed) ? imeInputTarget : null;
}
});
}
@@ -232,6 +247,7 @@
* @param statsToken The token tracking the current IME request.
* @return {@code true} when the show request can proceed.
*/
+ @GuardedBy("ImfLock.class")
boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken,
@InputMethodManager.ShowFlags int showFlags) {
if (mPolicy.mA11yRequestingNoSoftKeyboard || mPolicy.mImeHiddenByDisplayPolicy) {
@@ -258,6 +274,7 @@
* @param statsToken The token tracking the current IME request.
* @return {@code true} when the hide request can proceed.
*/
+ @GuardedBy("ImfLock.class")
boolean canHideIme(@NonNull ImeTracker.Token statsToken,
@InputMethodManager.HideFlags int hideFlags) {
if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
@@ -279,6 +296,7 @@
* Returns the show flags for IME. This translates from {@link InputMethodManager.ShowFlags}
* to {@link InputMethod.ShowFlags}.
*/
+ @GuardedBy("ImfLock.class")
@InputMethod.ShowFlags
int getShowFlagsForInputMethodServiceOnly() {
int flags = 0;
@@ -294,6 +312,7 @@
* Returns the show flags for IMM. This translates from {@link InputMethod.ShowFlags}
* to {@link InputMethodManager.ShowFlags}.
*/
+ @GuardedBy("ImfLock.class")
@InputMethodManager.ShowFlags
int getShowFlags() {
int flags = 0;
@@ -305,12 +324,14 @@
return flags;
}
+ @GuardedBy("ImfLock.class")
void clearImeShowFlags() {
mRequestedShowExplicitly = false;
mShowForced = false;
mInputShown = false;
}
+ @GuardedBy("ImfLock.class")
int computeImeDisplayId(@NonNull ImeTargetWindowState state, int displayId) {
final int displayToShowIme = computeImeDisplayIdForTarget(displayId, mImeDisplayValidator);
state.setImeDisplayId(displayToShowIme);
@@ -328,6 +349,7 @@
* visibility state, it could be {@link #STATE_SHOW_IME} or
* {@link #STATE_HIDE_IME}.
*/
+ @GuardedBy("ImfLock.class")
void requestImeVisibility(IBinder windowToken, boolean showIme) {
ImeTargetWindowState state = getOrCreateWindowState(windowToken);
if (!mPolicy.mPendingA11yRequestingHideKeyboard) {
@@ -343,6 +365,7 @@
setWindowStateInner(windowToken, state);
}
+ @GuardedBy("ImfLock.class")
ImeTargetWindowState getOrCreateWindowState(IBinder windowToken) {
ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
if (state == null) {
@@ -351,11 +374,13 @@
return state;
}
+ @GuardedBy("ImfLock.class")
ImeTargetWindowState getWindowStateOrNull(IBinder windowToken) {
ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
return state;
}
+ @GuardedBy("ImfLock.class")
void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
if (state != null && newState.hasEditorFocused()
@@ -367,6 +392,7 @@
setWindowStateInner(windowToken, newState);
}
+ @GuardedBy("ImfLock.class")
private void setWindowStateInner(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
if (DEBUG) Slog.d(TAG, "setWindowStateInner, windowToken=" + windowToken
+ ", state=" + newState);
@@ -391,6 +417,7 @@
}
}
+ @GuardedBy("ImfLock.class")
ImeVisibilityResult computeState(ImeTargetWindowState state, boolean allowVisible) {
// TODO: Output the request IME visibility state according to the requested window state
final int softInputVisibility = state.mSoftInputModeState & SOFT_INPUT_MASK_STATE;
@@ -540,6 +567,7 @@
return null;
}
+ @GuardedBy("ImfLock.class")
@VisibleForTesting
ImeVisibilityResult onInteractiveChanged(IBinder windowToken, boolean interactive) {
final ImeTargetWindowState state = getWindowStateOrNull(windowToken);
@@ -568,6 +596,7 @@
return userData.mImeBindingState.mFocusedWindow;
}
+ @GuardedBy("ImfLock.class")
IBinder getWindowTokenFrom(ImeTargetWindowState windowState) {
for (IBinder windowToken : mRequestWindowStateMap.keySet()) {
final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
@@ -578,6 +607,7 @@
return null;
}
+ @GuardedBy("ImfLock.class")
boolean shouldRestoreImeVisibility(@NonNull ImeTargetWindowState state) {
final int softInputMode = state.getSoftInputModeState();
switch (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
@@ -591,14 +621,17 @@
return mWindowManagerInternal.shouldRestoreImeVisibility(getWindowTokenFrom(state));
}
+ @GuardedBy("ImfLock.class")
boolean isInputShown() {
return mInputShown;
}
+ @GuardedBy("ImfLock.class")
void setInputShown(boolean inputShown) {
mInputShown = inputShown;
}
+ @GuardedBy("ImfLock.class")
void dumpDebug(ProtoOutputStream proto, long fieldId) {
proto.write(SHOW_EXPLICITLY_REQUESTED, mRequestedShowExplicitly);
proto.write(SHOW_FORCED, mShowForced);
@@ -607,6 +640,7 @@
proto.write(INPUT_SHOWN, mInputShown);
}
+ @GuardedBy("ImfLock.class")
void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
final Printer p = new PrintWriterPrinter(pw);
p.println(prefix + "mRequestedShowExplicitly=" + mRequestedShowExplicitly
@@ -629,12 +663,14 @@
*
* This prevents the IME from showing when it otherwise may have shown.
*/
+ @GuardedBy("ImfLock.class")
private boolean mImeHiddenByDisplayPolicy;
/**
* Set when the accessibility service requests to hide IME by
* {@link AccessibilityService.SoftKeyboardController#setShowMode}
*/
+ @GuardedBy("ImfLock.class")
private boolean mA11yRequestingNoSoftKeyboard;
/**
@@ -643,16 +679,20 @@
* {@link android.provider.Settings.Secure#ACCESSIBILITY_SOFT_KEYBOARD_MODE} without
* changing the requested IME visible state.
*/
+ @GuardedBy("ImfLock.class")
private boolean mPendingA11yRequestingHideKeyboard;
+ @GuardedBy("ImfLock.class")
void setImeHiddenByDisplayPolicy(boolean hideIme) {
mImeHiddenByDisplayPolicy = hideIme;
}
+ @GuardedBy("ImfLock.class")
boolean isImeHiddenByDisplayPolicy() {
return mImeHiddenByDisplayPolicy;
}
+ @GuardedBy("ImfLock.class")
void setA11yRequestNoSoftKeyboard(int keyboardShowMode) {
mA11yRequestingNoSoftKeyboard =
(keyboardShowMode & AccessibilityService.SHOW_MODE_MASK) == SHOW_MODE_HIDDEN;
@@ -661,11 +701,13 @@
}
}
+ @GuardedBy("ImfLock.class")
boolean isA11yRequestNoSoftKeyboard() {
return mA11yRequestingNoSoftKeyboard;
}
}
+ @GuardedBy("ImfLock.class")
ImeVisibilityPolicy getImePolicy() {
return mPolicy;
}
@@ -721,63 +763,78 @@
/**
* Set if the client has asked for the input method to be shown.
*/
+ @GuardedBy("ImfLock.class")
private boolean mRequestedImeVisible;
/**
* A identifier for knowing the requester of {@link InputMethodManager#showSoftInput} or
* {@link InputMethodManager#hideSoftInputFromWindow}.
*/
+ @GuardedBy("ImfLock.class")
private IBinder mRequestImeToken;
/**
* The IME target display id for which the latest startInput was called.
*/
+ @GuardedBy("ImfLock.class")
private int mImeDisplayId = DEFAULT_DISPLAY;
+ @AnyThread
boolean hasImeFocusChanged() {
return mImeFocusChanged;
}
+ @AnyThread
boolean hasEditorFocused() {
return mHasFocusedEditor;
}
+ @AnyThread
boolean isStartInputByGainFocus() {
return mIsStartInputByGainFocus;
}
+ @AnyThread
int getSoftInputModeState() {
return mSoftInputModeState;
}
+ @AnyThread
int getWindowFlags() {
return mWindowFlags;
}
+ @AnyThread
int getToolType() {
return mToolType;
}
+ @GuardedBy("ImfLock.class")
private void setImeDisplayId(int imeDisplayId) {
mImeDisplayId = imeDisplayId;
}
+ @GuardedBy("ImfLock.class")
int getImeDisplayId() {
return mImeDisplayId;
}
+ @GuardedBy("ImfLock.class")
private void setRequestedImeVisible(boolean requestedImeVisible) {
mRequestedImeVisible = requestedImeVisible;
}
+ @GuardedBy("ImfLock.class")
boolean isRequestedImeVisible() {
return mRequestedImeVisible;
}
+ @GuardedBy("ImfLock.class")
void setRequestImeToken(IBinder token) {
mRequestImeToken = token;
}
+ @GuardedBy("ImfLock.class")
IBinder getRequestImeToken() {
return mRequestImeToken;
}
diff --git a/services/core/java/com/android/server/inputmethod/ImmutableSparseArray.java b/services/core/java/com/android/server/inputmethod/ImmutableSparseArray.java
new file mode 100644
index 0000000..382aa8a
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ImmutableSparseArray.java
@@ -0,0 +1,183 @@
+/*
+ * 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.inputmethod;
+
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.util.function.Consumer;
+
+/**
+ * A holder object to expose {@link SparseArray} to multiple threads in a thread-safe manner through
+ * "final Field Semantics" defined in JLS 17.5, with only exposing thread-safe methods such as
+ * {@link SparseArray#get(int)} and {@link SparseArray#size()} from {@link SparseArray}, and with
+ * adding clone-with-update style methods {@link #cloneWithPutOrSelf(int, Object)} and
+ * {@link #cloneWithRemoveOrSelf(int)} instead of exposing mutation methods.
+ *
+ * @param <E> Type of the element
+ */
+final class ImmutableSparseArray<E> {
+ @NonNull
+ private final SparseArray<E> mArray;
+
+ private static final ImmutableSparseArray<Object> EMPTY =
+ new ImmutableSparseArray<>(new SparseArray<>());
+
+ /**
+ * Returns an empty {@link ImmutableSparseArray} instance.
+ *
+ * @return An empty {@link ImmutableSparseArray} instance.
+ * @param <T> Type of the element
+ */
+ @SuppressWarnings("unchecked")
+ @AnyThread
+ @NonNull
+ static <T> ImmutableSparseArray<T> empty() {
+ return (ImmutableSparseArray<T>) EMPTY;
+ }
+
+ private ImmutableSparseArray(@NonNull SparseArray<E> array) {
+ mArray = array;
+ }
+
+ /**
+ * @return the size of this array
+ */
+ @AnyThread
+ int size() {
+ return mArray.size();
+ }
+
+ /**
+ * Returns the key of the specified index.
+ *
+ * @return the key of the specified index
+ * @throws ArrayIndexOutOfBoundsException when the index is out of range
+ */
+ @AnyThread
+ int keyAt(int index) {
+ return mArray.keyAt(index);
+ }
+
+ /**
+ * Returns the value of the specified index.
+ *
+ * @return the value of the specified index
+ * @throws ArrayIndexOutOfBoundsException when the index is out of range
+ */
+ @AnyThread
+ @Nullable
+ public E valueAt(int index) {
+ return mArray.valueAt(index);
+ }
+
+ /**
+ * Returns the index of the specified key.
+ *
+ * @return the index of the specified key if exists. Otherwise {@code -1}
+ */
+ @AnyThread
+ int indexOfKey(int key) {
+ return mArray.indexOfKey(key);
+ }
+
+ /**
+ * Returns {@code true} if the given {@code key} exists.
+ *
+ * @param key the key to be queried
+ * @return {@code true} if the given {@code key} exists
+ */
+ @AnyThread
+ boolean contains(int key) {
+ return mArray.contains(key);
+ }
+
+ /**
+ * Returns the value associated with the {@code key}.
+ *
+ * @param key the key to be queried
+ * @return the value associated with the {@code key} if exists. Otherwise {@code null}
+ */
+ @AnyThread
+ @Nullable
+ E get(int key) {
+ return mArray.get(key);
+ }
+
+ /**
+ * Run {@link Consumer} for each value.
+ *
+ * @param consumer {@link Consumer} to be called back
+ */
+ @AnyThread
+ void forEach(@NonNull Consumer<E> consumer) {
+ final int size = mArray.size();
+ for (int i = 0; i < size; ++i) {
+ consumer.accept(mArray.valueAt(i));
+ }
+ }
+
+ /**
+ * Returns an instance of {@link ImmutableSparseArray} that has the given key and value on top
+ * of items cloned from this instance.
+ *
+ * @param key the key to be added
+ * @param value the value to be added
+ * @return the same {@link ImmutableSparseArray} instance if there is actually no update.
+ * Otherwise, a new instance of {@link ImmutableSparseArray}
+ */
+ @AnyThread
+ @NonNull
+ ImmutableSparseArray<E> cloneWithPutOrSelf(int key, @Nullable E value) {
+ final var prevKeyIndex = mArray.indexOfKey(key);
+ if (prevKeyIndex >= 0) {
+ final var prevValue = mArray.valueAt(prevKeyIndex);
+ if (prevValue == value) {
+ return this;
+ }
+ }
+ final var clone = mArray.clone();
+ clone.put(key, value);
+ return new ImmutableSparseArray<>(clone);
+ }
+
+ /**
+ * Returns an instance of {@link ImmutableSparseArray} that does not have the given key on top
+ * of items cloned from this instance.
+ *
+ * @param key the key to be removed
+ * @return the same {@link ImmutableSparseArray} instance if there is actually no update.
+ * Otherwise, a new instance of {@link ImmutableSparseArray}
+ */
+ @AnyThread
+ @NonNull
+ ImmutableSparseArray<E> cloneWithRemoveOrSelf(int key) {
+ final int index = indexOfKey(key);
+ if (index < 0) {
+ return this;
+ }
+ if (mArray.size() == 1) {
+ return empty();
+ }
+ final var clone = mArray.clone();
+ clone.remove(key);
+ return new ImmutableSparseArray<>(clone);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 9837ab1..03cbab5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -463,7 +463,7 @@
// should now try to restart the service for us.
mLastBindTime = SystemClock.uptimeMillis();
clearCurMethodAndSessions();
- mService.clearInputShownLocked();
+ mService.mVisibilityStateComputer.setInputShown(false);
mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME, mUserId);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ed71765..8b21ff3 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -258,7 +258,6 @@
private static final int MSG_HIDE_ALL_INPUT_METHODS = 1035;
private static final int MSG_REMOVE_IME_SURFACE = 1060;
private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061;
- private static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070;
private static final int MSG_RESET_HANDWRITING = 1090;
private static final int MSG_START_HANDWRITING = 1100;
@@ -271,7 +270,6 @@
private static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
- private static final int MSG_SYSTEM_UNLOCK_USER = 5000;
private static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010;
private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
@@ -340,6 +338,35 @@
return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentUserId;
}
+ /**
+ * Figures out the target IME user ID associated with the given {@code displayId}.
+ *
+ * @param displayId the display ID to be queried about
+ * @return User ID to be used for this {@code displayId}.
+ */
+ @GuardedBy("ImfLock.class")
+ @UserIdInt
+ private int resolveImeUserIdFromDisplayIdLocked(int displayId) {
+ return mConcurrentMultiUserModeEnabled
+ ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentUserId;
+ }
+
+ /**
+ * Figures out the target IME user ID associated with the given {@code windowToken}.
+ *
+ * @param windowToken the Window token to be queried about
+ * @return User ID to be used for this {@code displayId}.
+ */
+ @GuardedBy("ImfLock.class")
+ @UserIdInt
+ private int resolveImeUserIdFromWindowLocked(@NonNull IBinder windowToken) {
+ if (mConcurrentMultiUserModeEnabled) {
+ final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
+ return mUserManagerInternal.getUserAssignedToDisplay(displayId);
+ }
+ return mCurrentUserId;
+ }
+
final Context mContext;
final Resources mRes;
private final Handler mHandler;
@@ -373,7 +400,7 @@
@GuardedBy("ImfLock.class")
@MultiUserUnawareField
@NonNull
- private final ImeVisibilityStateComputer mVisibilityStateComputer;
+ final ImeVisibilityStateComputer mVisibilityStateComputer;
@GuardedBy("ImfLock.class")
@SharedByAllUsersField
@@ -570,13 +597,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 +705,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);
}
}
}
@@ -763,7 +787,6 @@
.getMethodMap();
synchronized (ImfLock.class) {
- final boolean isCurrentUser = (userId == mCurrentUserId);
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
@@ -786,14 +809,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());
@@ -822,9 +838,6 @@
final InputMethodSettings newSettings =
InputMethodSettings.create(newMethodMap, userId);
InputMethodSettingsRepository.put(userId, newSettings);
- if (!isCurrentUser) {
- return;
- }
postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
boolean changed = false;
@@ -959,7 +972,7 @@
// TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
// additional subtypes in switchUserOnHandlerLocked().
final ServiceThread thread = new ServiceThread(HANDLER_THREAD_NAME,
- Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+ Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
thread.start();
final ServiceThread ioThread = new ServiceThread(PACKAGE_MONITOR_THREAD_NAME,
@@ -1034,10 +1047,24 @@
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
- // Called on ActivityManager thread.
- SecureSettingsWrapper.onUserUnlocking(user.getUserIdentifier());
- mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER, user.getUserIdentifier(), 0)
- .sendToTarget();
+ // Called on ActivityManager thread. Do not block the calling thread.
+ final int userId = user.getUserIdentifier();
+ SecureSettingsWrapper.onUserUnlocking(userId);
+ mService.mIoHandler.post(() -> {
+ final var settings = queryInputMethodServicesInternal(mService.mContext, userId,
+ AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
+ synchronized (ImfLock.class) {
+ if (!mService.mSystemReady) {
+ return;
+ }
+ // We need to rebuild IMEs.
+ mService.postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */,
+ userId);
+ mService.updateInputMethodsFromSettingsLocked(true /* enabledChanged */,
+ userId);
+ }
+ });
}
@Override
@@ -1047,10 +1074,8 @@
SecureSettingsWrapper.onUserStarting(userId);
mService.mIoHandler.post(() -> {
synchronized (ImfLock.class) {
- if (mService.mConcurrentMultiUserModeEnabled) {
- if (mService.mCurrentUserId != userId && mService.mSystemReady) {
- mService.initializeVisibleBackgroundUserLocked(userId);
- }
+ if (mService.mSystemReady) {
+ mService.onUserReadyLocked(userId);
}
}
});
@@ -1066,8 +1091,8 @@
for (int userId : userIds) {
Slog.d(TAG, "Start initialization for user=" + userId);
- final var additionalSubtypeMap =
- AdditionalSubtypeMapRepository.ensureInitializedAndGet(userId);
+ AdditionalSubtypeMapRepository.initializeIfNecessary(userId);
+ final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
final var settings = InputMethodManagerService.queryInputMethodServicesInternal(
context, userId, additionalSubtypeMap,
DirectBootAwareness.AUTO).getMethodMap();
@@ -1088,23 +1113,6 @@
}
}
- void onUnlockUser(@UserIdInt int userId) {
- synchronized (ImfLock.class) {
- if (DEBUG) {
- Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + mCurrentUserId);
- }
- if (!mSystemReady) {
- return;
- }
- final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
- userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
- InputMethodSettingsRepository.put(userId, newSettings);
- // We need to rebuild IMEs.
- postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
- updateInputMethodsFromSettingsLocked(true /* enabledChanged */, userId);
- }
- }
-
@GuardedBy("ImfLock.class")
void scheduleSwitchUserTaskLocked(@UserIdInt int userId,
@Nullable IInputMethodClientInvoker clientToBeReset) {
@@ -1113,7 +1121,7 @@
mUserSwitchHandlerTask.mClientToBeReset = clientToBeReset;
return;
}
- mHandler.removeCallbacks(mUserSwitchHandlerTask);
+ mIoHandler.removeCallbacks(mUserSwitchHandlerTask);
}
// Hide soft input before user switch task since switch task may block main handler a while
// and delayed the hideCurrentInputLocked().
@@ -1123,7 +1131,7 @@
final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
clientToBeReset);
mUserSwitchHandlerTask = task;
- mHandler.post(task);
+ mIoHandler.post(task);
}
@VisibleForTesting
@@ -1404,30 +1412,32 @@
UserHandle.ALL, broadcastFilterForAllUsers, null, null,
Context.RECEIVER_EXPORTED);
- final String defaultImiId = SecureSettingsWrapper.getString(
- Settings.Secure.DEFAULT_INPUT_METHOD, null, currentUserId);
- final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
- final var settings = InputMethodSettingsRepository.get(currentUserId);
- postInputMethodSettingUpdatedLocked(
- !imeSelectedOnBoot /* resetDefaultEnabledIme */, currentUserId);
- updateFromSettingsLocked(true, currentUserId);
- InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
- getPackageManagerForUser(mContext, currentUserId),
- settings.getEnabledInputMethodList());
-
AdditionalSubtypeMapRepository.startWriterThread();
- if (mConcurrentMultiUserModeEnabled) {
- for (int userId : mUserManagerInternal.getUserIds()) {
- if (userId != mCurrentUserId) {
- initializeVisibleBackgroundUserLocked(userId);
- }
- }
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ onUserReadyLocked(userId);
}
}
}
}
+ @GuardedBy("ImfLock.class")
+ void onUserReadyLocked(@UserIdInt int userId) {
+ if (!mUserManagerInternal.isUserRunning(userId)) {
+ return;
+ }
+
+ final String defaultImiId = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, userId);
+ final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
+ final var settings = InputMethodSettingsRepository.get(userId);
+ postInputMethodSettingUpdatedLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */,
+ userId);
+ updateFromSettingsLocked(true, userId);
+ InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
+ getPackageManagerForUser(mContext, userId), settings.getEnabledInputMethodList());
+ }
+
void registerImeRequestedChangedListener() {
mWindowManagerInternal.setOnImeRequestedChangedListener(
(windowToken, imeVisible, statsToken) -> {
@@ -1825,17 +1835,6 @@
}
@GuardedBy("ImfLock.class")
- void clearInputShownLocked() {
- mVisibilityStateComputer.setInputShown(false);
- }
-
- @GuardedBy("ImfLock.class")
- @Override
- public boolean isInputShownLocked() {
- return mVisibilityStateComputer.isInputShown();
- }
-
- @GuardedBy("ImfLock.class")
private boolean isShowRequestedForCurrentWindow(@UserIdInt int userId) {
final var userData = getUserData(userId);
// TODO(b/349904272): Make mVisibilityStateComputer multi-user aware
@@ -2762,16 +2761,14 @@
}
}
- private void updateImeWindowStatus(boolean disableImeIcon) {
- synchronized (ImfLock.class) {
- // TODO(b/350386877): Propagate userId from the caller.
- final int userId = mCurrentUserId;
- if (disableImeIcon) {
- final var bindingController = getInputMethodBindingController(userId);
- updateSystemUiLocked(0, bindingController.getBackDisposition(), userId);
- } else {
- updateSystemUiLocked(userId);
- }
+ @GuardedBy("ImfLock.class")
+ private void updateImeWindowStatusLocked(boolean disableImeIcon, int displayId) {
+ final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
+ if (disableImeIcon) {
+ final var bindingController = getInputMethodBindingController(userId);
+ updateSystemUiLocked(0, bindingController.getBackDisposition(), userId);
+ } else {
+ updateSystemUiLocked(userId);
}
}
@@ -2844,60 +2841,6 @@
}
}
- /**
- * This initialization logic is used when and only when {@link #mConcurrentMultiUserModeEnabled}
- * is set to {@code true}.
- *
- * <p>There remain several yet-to-be-implemented features. For the canonical and desired
- * behaviors always refer to single-user code paths such as
- * {@link #updateInputMethodsFromSettingsLocked(boolean, int)}.</p>
- *
- * <p>Here are examples of missing features.</p>
- * <ul>
- * <li>Profiles are not supported.</li>
- * <li>
- * {@link PackageManager#COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED} is not updated.
- * </li>
- * <li>{@link InputMethodBindingController#getDeviceIdToShowIme()} is ignored.</li>
- * <li>and so on.</li>
- * </ul>
- */
- @GuardedBy("ImfLock.class")
- void initializeVisibleBackgroundUserLocked(@UserIdInt int userId) {
- final var settings = InputMethodSettingsRepository.get(userId);
-
- // Until we figure out what makes most sense, we enable all the pre-installed IMEs in
- // concurrent multi-user IME mode.
- String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
- for (var imi : settings.getMethodList()) {
- if (!imi.isSystem()) {
- continue;
- }
- enabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(enabledImeIdsStr, imi.getId());
- }
- if (!TextUtils.equals(settings.getEnabledInputMethodsStr(), enabledImeIdsStr)) {
- settings.putEnabledInputMethodsStr(enabledImeIdsStr);
- }
-
- // Also update the currently-selected IME.
- String id = settings.getSelectedInputMethod();
- if (TextUtils.isEmpty(id)) {
- final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
- settings.getEnabledInputMethodList());
- if (imi != null) {
- id = imi.getId();
- settings.putSelectedInputMethod(id);
- }
- }
- final var userData = getUserData(userId);
- final var bindingController = userData.mBindingController;
- bindingController.setSelectedMethodId(id);
-
- // Also re-initialize controllers.
- userData.mSwitchingController.resetCircularListLocked(mContext, settings);
- userData.mHardwareKeyboardShortcutController.update(settings);
- }
-
@GuardedBy("ImfLock.class")
void updateInputMethodsFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
@@ -3076,52 +3019,75 @@
}
}
+ @GuardedBy("ImfLock.class")
+ private void sendResultReceiverFailureLocked(@Nullable ResultReceiver resultReceiver) {
+ final boolean isInputShown = mVisibilityStateComputer.isInputShown();
+ resultReceiver.send(isInputShown
+ ? InputMethodManager.RESULT_UNCHANGED_SHOWN
+ : InputMethodManager.RESULT_UNCHANGED_HIDDEN, null);
+ }
+
@Override
public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
@NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
int lastClickToolType, ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#showSoftInput", mDumper);
synchronized (ImfLock.class) {
- final int userId = resolveImeUserIdLocked(callingUserId);
- if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken,
- userId)) {
- ImeTracker.forLogging().onFailed(
- statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- return false;
+ final boolean result = showSoftInputLocked(client, windowToken, statsToken, flags,
+ lastClickToolType, resultReceiver, reason);
+ // When ZeroJankProxy is enabled, the app has already received "true" as the return
+ // value, and expect "resultReceiver" to be notified later. See b/327751155.
+ if (!result && Flags.useZeroJankProxy()) {
+ sendResultReceiverFailureLocked(resultReceiver);
}
- final long ident = Binder.clearCallingIdentity();
- final var userData = getUserData(userId);
- try {
- if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
- if (Flags.refactorInsetsController()) {
- boolean wasVisible = isInputShownLocked();
- if (userData.mImeBindingState != null
- && userData.mImeBindingState.mFocusedWindowClient != null
- && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
- userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(true, statsToken);
- if (resultReceiver != null) {
- resultReceiver.send(
- wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
- : InputMethodManager.RESULT_SHOWN, null);
- }
- return true;
+ return result; // ignored when ZeroJankProxy is enabled.
+ }
+ }
+
+ @GuardedBy("ImfLock.class")
+ private boolean showSoftInputLocked(IInputMethodClient client, IBinder windowToken,
+ @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+ int lastClickToolType, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason) {
+ final int uid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getUserId(uid);
+ final int userId = resolveImeUserIdLocked(callingUserId);
+ if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken,
+ userId)) {
+ ImeTracker.forLogging().onFailed(
+ statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ return false;
+ }
+ final var userData = getUserData(userId);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
+ if (Flags.refactorInsetsController()) {
+ boolean wasVisible = mVisibilityStateComputer.isInputShown();
+ if (userData.mImeBindingState != null
+ && userData.mImeBindingState.mFocusedWindowClient != null
+ && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
+ userData.mImeBindingState.mFocusedWindowClient.mClient
+ .setImeVisibility(true, statsToken);
+ if (resultReceiver != null) {
+ resultReceiver.send(
+ wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
+ : InputMethodManager.RESULT_SHOWN, null);
}
- return false;
- } else {
- return showCurrentInputLocked(windowToken, statsToken, flags, lastClickToolType,
- resultReceiver, reason, userId);
+ return true;
}
- } finally {
- Binder.restoreCallingIdentity(ident);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ return false;
+ } else {
+ return showCurrentInputLocked(windowToken, statsToken, flags, lastClickToolType,
+ resultReceiver, reason, userId);
}
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -3131,8 +3097,7 @@
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#showSoftInput", mDumper);
synchronized (ImfLock.class) {
- // TODO(b/305849394): Infer userId from windowToken
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
final long ident = Binder.clearCallingIdentity();
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
@@ -3152,8 +3117,7 @@
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#hideSoftInput", mDumper);
synchronized (ImfLock.class) {
- // TODO(b/305849394): Infer userId from windowToken
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
final long ident = Binder.clearCallingIdentity();
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
@@ -3415,14 +3379,14 @@
Objects.requireNonNull(windowToken, "windowToken must not be null");
synchronized (ImfLock.class) {
Boolean windowPerceptible = mFocusedWindowPerceptible.get(windowToken);
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
final var userData = getUserData(userId);
if (userData.mImeBindingState.mFocusedWindow != windowToken
|| (windowPerceptible != null && windowPerceptible == perceptible)) {
return;
}
mFocusedWindowPerceptible.put(windowToken, windowPerceptible);
- updateSystemUiLocked(mCurrentUserId);
+ updateSystemUiLocked(userId);
}
});
}
@@ -3516,50 +3480,64 @@
public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
@NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#hideSoftInput", mDumper);
synchronized (ImfLock.class) {
- final int userId = resolveImeUserIdLocked(callingUserId);
- if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken, userId)) {
- if (isInputShownLocked()) {
- ImeTracker.forLogging().onFailed(
- statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
- } else {
- ImeTracker.forLogging().onCancelled(statsToken,
- ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ final boolean result = hideSoftInputLocked(client, windowToken, statsToken, flags,
+ resultReceiver, reason);
+ // When ZeroJankProxy is enabled, the app has already received "true" as the return
+ // value, and expect "resultReceiver" to be notified later. See b/327751155.
+ if (!result && Flags.useZeroJankProxy()) {
+ sendResultReceiverFailureLocked(resultReceiver);
+ }
+ return result; // ignored when ZeroJankProxy is enabled.
+ }
+ }
+
+ @GuardedBy("ImfLock.class")
+ private boolean hideSoftInputLocked(IInputMethodClient client, IBinder windowToken,
+ @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ final int uid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getUserId(uid);
+ final int userId = resolveImeUserIdLocked(callingUserId);
+ if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken, userId)) {
+ if (mVisibilityStateComputer.isInputShown()) {
+ ImeTracker.forLogging().onFailed(
+ statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ } else {
+ ImeTracker.forLogging().onCancelled(statsToken,
+ ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ }
+ return false;
+ }
+ final var userData = getUserData(userId);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput");
+ if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
+ if (Flags.refactorInsetsController()) {
+ if (userData.mImeBindingState != null
+ && userData.mImeBindingState.mFocusedWindowClient != null
+ && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
+ boolean wasVisible = mVisibilityStateComputer.isInputShown();
+ // TODO add windowToken to interface
+ userData.mImeBindingState.mFocusedWindowClient.mClient
+ .setImeVisibility(false, statsToken);
+ if (resultReceiver != null) {
+ resultReceiver.send(wasVisible ? InputMethodManager.RESULT_HIDDEN
+ : InputMethodManager.RESULT_UNCHANGED_HIDDEN, null);
+ }
+ return true;
}
return false;
+ } else {
+ return InputMethodManagerService.this.hideCurrentInputLocked(
+ windowToken, statsToken, flags, resultReceiver, reason, userId);
}
- final long ident = Binder.clearCallingIdentity();
- final var userData = getUserData(userId);
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput");
- if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
- if (Flags.refactorInsetsController()) {
- if (userData.mImeBindingState != null
- && userData.mImeBindingState.mFocusedWindowClient != null
- && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
- boolean wasVisible = isInputShownLocked();
- // TODO add windowToken to interface
- userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(false, statsToken);
- if (resultReceiver != null) {
- resultReceiver.send(wasVisible ? InputMethodManager.RESULT_HIDDEN
- : InputMethodManager.RESULT_UNCHANGED_HIDDEN, null);
- }
- return true;
- }
- return false;
- } else {
- return InputMethodManagerService.this.hideCurrentInputLocked(windowToken,
- statsToken, flags, resultReceiver, reason, userId);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -3604,7 +3582,7 @@
// TODO(b/246309664): Clean up IMMS#mImeWindowVis
IInputMethodInvoker curMethod = bindingController.getCurMethod();
final boolean shouldHideSoftInput = curMethod != null
- && (isInputShownLocked()
+ && (mVisibilityStateComputer.isInputShown()
|| (bindingController.getImeWindowVis() & InputMethodService.IME_ACTIVE) != 0);
mVisibilityStateComputer.requestImeVisibility(windowToken, false);
@@ -3976,6 +3954,10 @@
@Override
public void showInputMethodPickerFromClient(IInputMethodClient client,
int auxiliarySubtypeMode) {
+ if (mConcurrentMultiUserModeEnabled) {
+ Slog.w(TAG, "showInputMethodPickerFromClient is not enabled on automotive");
+ return;
+ }
final int callingUserId = UserHandle.getCallingUserId();
synchronized (ImfLock.class) {
if (!canShowInputMethodPickerLocked(client)) {
@@ -4088,7 +4070,7 @@
@Override
public void onImeSwitchButtonClickFromSystem(int displayId) {
synchronized (ImfLock.class) {
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
final var userData = getUserData(userId);
final var bindingController = userData.mBindingController;
final var curToken = bindingController.getCurToken();
@@ -4930,14 +4912,12 @@
return mVisibilityApplier;
}
- void onApplyImeVisibilityFromComputer(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
- @NonNull ImeVisibilityResult result) {
- synchronized (ImfLock.class) {
- // TODO(b/305849394): Infer userId from windowToken
- final int userId = mCurrentUserId;
- mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(),
- result.getReason(), userId);
- }
+ @GuardedBy("ImfLock.class")
+ void onApplyImeVisibilityFromComputerLocked(IBinder windowToken,
+ @NonNull ImeTracker.Token statsToken, @NonNull ImeVisibilityResult result) {
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
+ mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(),
+ result.getReason(), userId);
}
@GuardedBy("ImfLock.class")
@@ -5007,7 +4987,7 @@
// implemented so that auxiliary subtypes will be excluded when the soft
// keyboard is invisible.
synchronized (ImfLock.class) {
- showAuxSubtypes = isInputShownLocked();
+ showAuxSubtypes = mVisibilityStateComputer.isInputShown();
}
break;
case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
@@ -5111,8 +5091,7 @@
case MSG_REMOVE_IME_SURFACE_FROM_WINDOW: {
IBinder windowToken = (IBinder) msg.obj;
synchronized (ImfLock.class) {
- // TODO(b/305849394): Infer userId from windowToken.
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
final var userData = getUserData(userId);
try {
if (windowToken == userData.mImeBindingState.mFocusedWindow
@@ -5125,10 +5104,6 @@
}
return true;
}
- case MSG_UPDATE_IME_WINDOW_STATUS: {
- updateImeWindowStatus(msg.arg1 == 1);
- return true;
- }
// ---------------------------------------------------------
@@ -5145,11 +5120,6 @@
sendOnNavButtonFlagsChangedToAllImesLocked();
}
return true;
- case MSG_SYSTEM_UNLOCK_USER: {
- final int userId = msg.arg1;
- onUnlockUser(userId);
- return true;
- }
case MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED: {
final int userId = msg.arg1;
final List<InputMethodInfo> imes = (List<InputMethodInfo>) msg.obj;
@@ -5873,22 +5843,7 @@
if (!settings.getMethodMap().containsKey(imeId)) {
return false; // IME is not found.
}
- if (userId == mCurrentUserId) {
- setInputMethodEnabledLocked(imeId, enabled, userId);
- return true;
- }
- if (enabled) {
- final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
- final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
- enabledImeIdsStr, imeId);
- if (!TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr)) {
- settings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
- }
- } else {
- settings.buildAndPutEnabledInputMethodsStrRemovingId(
- new StringBuilder(),
- settings.getEnabledInputMethodsAndSubtypeList(), imeId);
- }
+ setInputMethodEnabledLocked(imeId, enabled, userId);
return true;
}
}
@@ -5936,8 +5891,7 @@
@Override
public void reportImeControl(@Nullable IBinder windowToken) {
synchronized (ImfLock.class) {
- // TODO(b/305849394): Need to infer userId or get userId from callers.
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
final var userData = getUserData(userId);
if (userData.mImeBindingState.mFocusedWindow != windowToken) {
// A perceptible value was set for the focused window, but it is no longer in
@@ -5952,8 +5906,7 @@
@Override
public void onImeParentChanged(int displayId) {
synchronized (ImfLock.class) {
- // TODO(b/305849394): Need to infer userId or get userId from callers.
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
final var userData = getUserData(userId);
// Hide the IME method menu only when the IME surface parent is changed by the
// input target changed, in case seeing the dialog dismiss flickering during
@@ -5978,8 +5931,11 @@
@ImfLockFree
@Override
public void updateImeWindowStatus(boolean disableImeIcon, int displayId) {
- mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0)
- .sendToTarget();
+ mHandler.post(() -> {
+ synchronized (ImfLock.class) {
+ updateImeWindowStatusLocked(disableImeIcon, displayId);
+ }
+ });
}
@Override
@@ -6077,8 +6033,8 @@
public void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
IBinder targetWindowToken) {
synchronized (ImfLock.class) {
- // TODO(b/305849394): Infer userId from displayId
- switchKeyboardLayoutLocked(direction, getUserData(mCurrentUserId));
+ final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
+ switchKeyboardLayoutLocked(direction, getUserData(userId));
}
}
}
@@ -6696,36 +6652,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 +6662,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/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
index 1b84036..4f5af63 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -19,15 +19,13 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
final class InputMethodSettingsRepository {
- // TODO(b/352594784): Should we user other lock primitives?
- @GuardedBy("sPerUserMap")
+ private static final Object sMutationLock = new Object();
+
@NonNull
- private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+ private static volatile ImmutableSparseArray<InputMethodSettings> sPerUserMap =
+ ImmutableSparseArray.empty();
/**
* Not intended to be instantiated.
@@ -38,10 +36,7 @@
@NonNull
@AnyThread
static InputMethodSettings get(@UserIdInt int userId) {
- final InputMethodSettings obj;
- synchronized (sPerUserMap) {
- obj = sPerUserMap.get(userId);
- }
+ final InputMethodSettings obj = sPerUserMap.get(userId);
if (obj != null) {
return obj;
}
@@ -50,15 +45,15 @@
@AnyThread
static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
- synchronized (sPerUserMap) {
- sPerUserMap.put(userId, obj);
+ synchronized (sMutationLock) {
+ sPerUserMap = sPerUserMap.cloneWithPutOrSelf(userId, obj);
}
}
@AnyThread
static void remove(@UserIdInt int userId) {
- synchronized (sPerUserMap) {
- sPerUserMap.remove(userId);
+ synchronized (sMutationLock) {
+ sPerUserMap = sPerUserMap.cloneWithRemoveOrSelf(userId);
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
index e7cff20..b3500be 100644
--- a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
+++ b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
@@ -24,7 +24,6 @@
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
@@ -38,6 +37,13 @@
* to the persistent value when the user storage is unlocked.</p>
*/
final class SecureSettingsWrapper {
+
+ private static final Object sMutationLock = new Object();
+
+ @NonNull
+ private static volatile ImmutableSparseArray<ReaderWriter> sUserMap =
+ ImmutableSparseArray.empty();
+
@Nullable
private static volatile ContentResolver sContentResolver = null;
@@ -61,8 +67,8 @@
*/
@AnyThread
static void endTestMode() {
- synchronized (sUserMap) {
- sUserMap.clear();
+ synchronized (sMutationLock) {
+ sUserMap = ImmutableSparseArray.empty();
}
sTestMode = false;
}
@@ -243,10 +249,6 @@
}
}
- @GuardedBy("sUserMap")
- @NonNull
- private static final SparseArray<ReaderWriter> sUserMap = new SparseArray<>();
-
private static final ReaderWriter NOOP = new ReaderWriter() {
@Override
public void putString(String key, String str) {
@@ -282,15 +284,15 @@
private static ReaderWriter putOrGet(@UserIdInt int userId,
@NonNull ReaderWriter readerWriter) {
final boolean isUnlockedUserImpl = readerWriter instanceof UnlockedUserImpl;
- synchronized (sUserMap) {
+ synchronized (sMutationLock) {
final ReaderWriter current = sUserMap.get(userId);
if (current == null) {
- sUserMap.put(userId, readerWriter);
+ sUserMap = sUserMap.cloneWithPutOrSelf(userId, readerWriter);
return readerWriter;
}
// Upgrading from CopyOnWriteImpl to DirectImpl is allowed.
if (current instanceof LockedUserImpl && isUnlockedUserImpl) {
- sUserMap.put(userId, readerWriter);
+ sUserMap = sUserMap.cloneWithPutOrSelf(userId, readerWriter);
return readerWriter;
}
return current;
@@ -300,11 +302,9 @@
@NonNull
@AnyThread
private static ReaderWriter get(@UserIdInt int userId) {
- synchronized (sUserMap) {
- final ReaderWriter readerWriter = sUserMap.get(userId);
- if (readerWriter != null) {
- return readerWriter;
- }
+ final ReaderWriter readerWriter = sUserMap.get(userId);
+ if (readerWriter != null) {
+ return readerWriter;
}
if (sTestMode) {
return putOrGet(userId, new FakeReaderWriterImpl());
@@ -363,8 +363,8 @@
*/
@AnyThread
static void onUserRemoved(@UserIdInt int userId) {
- synchronized (sUserMap) {
- sUserMap.remove(userId);
+ synchronized (sMutationLock) {
+ sUserMap = sUserMap.cloneWithRemoveOrSelf(userId);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 6f831cc..e3524b1 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -19,51 +19,39 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.util.SparseArray;
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.function.IntFunction;
final class UserDataRepository {
- private final ReentrantReadWriteLock mUserDataLock = new ReentrantReadWriteLock();
+ private final Object mMutationLock = new Object();
- @GuardedBy("mUserDataLock")
- private final SparseArray<UserData> mUserData = new SparseArray<>();
+ @NonNull
+ private volatile ImmutableSparseArray<UserData> mUserData = ImmutableSparseArray.empty();
private final IntFunction<InputMethodBindingController> mBindingControllerFactory;
@AnyThread
@NonNull
UserData getOrCreate(@UserIdInt int userId) {
- mUserDataLock.writeLock().lock();
- try {
- UserData userData = mUserData.get(userId);
- if (userData == null) {
- userData = new UserData(userId, mBindingControllerFactory.apply(userId));
- mUserData.put(userId, userData);
- }
+ // Do optimistic read first for optimization.
+ final var userData = mUserData.get(userId);
+ if (userData != null) {
return userData;
- } finally {
- mUserDataLock.writeLock().unlock();
+ }
+ // Note that the below line can be called concurrently. Here we assume that
+ // instantiating UserData for the same user multiple times would have no side effect.
+ final var newUserData = new UserData(userId, mBindingControllerFactory.apply(userId));
+ synchronized (mMutationLock) {
+ mUserData = mUserData.cloneWithPutOrSelf(userId, newUserData);
+ return newUserData;
}
}
@AnyThread
void forAllUserData(Consumer<UserData> consumer) {
- final SparseArray<UserData> copiedArray;
- mUserDataLock.readLock().lock();
- try {
- copiedArray = mUserData.clone();
- } finally {
- mUserDataLock.readLock().unlock();
- }
- for (int i = 0; i < copiedArray.size(); i++) {
- consumer.accept(copiedArray.valueAt(i));
- }
+ mUserData.forEach(consumer);
}
UserDataRepository(
@@ -73,11 +61,8 @@
@AnyThread
void remove(@UserIdInt int userId) {
- mUserDataLock.writeLock().lock();
- try {
- mUserData.remove(userId);
- } finally {
- mUserDataLock.writeLock().unlock();
+ synchronized (mMutationLock) {
+ mUserData = mUserData.cloneWithRemoveOrSelf(userId);
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 770e12d..fdb2e6f 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -86,8 +86,6 @@
interface Callback extends IInputMethodManagerImpl.Callback {
@GuardedBy("ImfLock.class")
ClientState getClientStateLocked(IInputMethodClient client);
- @GuardedBy("ImfLock.class")
- boolean isInputShownLocked();
}
private final Callback mInner;
@@ -178,19 +176,8 @@
@Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
@MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
- offload(
- () -> {
- if (!mInner.showSoftInput(
- client,
- windowToken,
- statsToken,
- flags,
- lastClickToolType,
- resultReceiver,
- reason)) {
- sendResultReceiverFailure(resultReceiver);
- }
- });
+ offload(() -> mInner.showSoftInput(
+ client, windowToken, statsToken, flags, lastClickToolType, resultReceiver, reason));
return true;
}
@@ -198,30 +185,11 @@
public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
@Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- offload(
- () -> {
- if (!mInner.hideSoftInput(
- client, windowToken, statsToken, flags, resultReceiver, reason)) {
- sendResultReceiverFailure(resultReceiver);
- }
- });
+ offload(() -> mInner.hideSoftInput(
+ client, windowToken, statsToken, flags, resultReceiver, reason));
return true;
}
- private void sendResultReceiverFailure(@Nullable ResultReceiver resultReceiver) {
- if (resultReceiver == null) {
- return;
- }
- final boolean isInputShown;
- synchronized (ImfLock.class) {
- isInputShown = mInner.isInputShownLocked();
- }
- resultReceiver.send(isInputShown
- ? InputMethodManager.RESULT_UNCHANGED_SHOWN
- : InputMethodManager.RESULT_UNCHANGED_HIDDEN,
- null);
- }
-
@Override
@IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
public void hideSoftInputFromServerForTest() {
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 9acf030..1070f2f 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -328,6 +328,12 @@
}
}
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.MEDIA_ROUTING_CONTROL,
+ Manifest.permission.MEDIA_CONTENT_CONTROL
+ },
+ conditional = true)
public void updateScanningState(
@NonNull IMediaRouter2 router, @ScanningState int scanningState) {
Objects.requireNonNull(router, "router must not be null");
@@ -1216,6 +1222,12 @@
disposeUserIfNeededLocked(userRecord); // since router removed from user
}
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.MEDIA_ROUTING_CONTROL,
+ Manifest.permission.MEDIA_CONTENT_CONTROL
+ },
+ conditional = true)
@GuardedBy("mLock")
private void updateScanningStateLocked(
@NonNull IMediaRouter2 router, @ScanningState int scanningState) {
@@ -1226,7 +1238,11 @@
return;
}
+ boolean enableScanViaMediaContentControl =
+ Flags.enableFullScanWithMediaContentControl()
+ && routerRecord.mHasMediaContentControlPermission;
if (scanningState == SCANNING_STATE_SCANNING_FULL
+ && !enableScanViaMediaContentControl
&& !routerRecord.mHasMediaRoutingControl) {
throw new SecurityException("Screen off scan requires MEDIA_ROUTING_CONTROL");
}
@@ -1679,7 +1695,11 @@
return;
}
+ boolean enableScanViaMediaContentControl =
+ Flags.enableFullScanWithMediaContentControl()
+ && managerRecord.mHasMediaContentControl;
if (!managerRecord.mHasMediaRoutingControl
+ && !enableScanViaMediaContentControl
&& scanningState == SCANNING_STATE_SCANNING_FULL) {
throw new SecurityException("Screen off scan requires MEDIA_ROUTING_CONTROL");
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 1188a07..363b8e4 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -443,6 +443,12 @@
// Binder call
@Override
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.MEDIA_ROUTING_CONTROL,
+ Manifest.permission.MEDIA_CONTENT_CONTROL
+ },
+ conditional = true)
public void updateScanningStateWithRouter2(
IMediaRouter2 router, @ScanningState int scanningState) {
mService2.updateScanningState(router, scanningState);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index b12a917..95d8bb9 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -2811,8 +2811,9 @@
private final class H extends Handler {
private static final int MSG_DISPATCH = 1;
private static final int MSG_METRICS = 2;
- private static final int MSG_RINGER_AUDIO = 5;
private static final int MSG_APPLY_EFFECTS = 6;
+ private static final int MSG_AUDIO_APPLIED_TO_RINGER = 7;
+ private static final int MSG_AUDIO_NOT_APPLIED_TO_RINGER = 8;
private static final long METRICS_PERIOD_MS = 6 * 60 * 60 * 1000;
@@ -2831,8 +2832,13 @@
}
private void postUpdateRingerAndAudio(boolean shouldApplyToRinger) {
- removeMessages(MSG_RINGER_AUDIO);
- sendMessage(obtainMessage(MSG_RINGER_AUDIO, shouldApplyToRinger));
+ if (shouldApplyToRinger) {
+ removeMessages(MSG_AUDIO_APPLIED_TO_RINGER);
+ sendEmptyMessage(MSG_AUDIO_APPLIED_TO_RINGER);
+ } else {
+ removeMessages(MSG_AUDIO_NOT_APPLIED_TO_RINGER);
+ sendEmptyMessage(MSG_AUDIO_NOT_APPLIED_TO_RINGER);
+ }
}
private void postApplyDeviceEffects(@ConfigChangeOrigin int origin) {
@@ -2849,9 +2855,11 @@
case MSG_METRICS:
mMetrics.emit();
break;
- case MSG_RINGER_AUDIO:
- boolean shouldApplyToRinger = (boolean) msg.obj;
- updateRingerAndAudio(shouldApplyToRinger);
+ case MSG_AUDIO_APPLIED_TO_RINGER:
+ updateRingerAndAudio(/* shouldApplyToRinger= */ true);
+ break;
+ case MSG_AUDIO_NOT_APPLIED_TO_RINGER:
+ updateRingerAndAudio(/* shouldApplyToRinger= */ false);
break;
case MSG_APPLY_EFFECTS:
@ConfigChangeOrigin int origin = msg.arg1;
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 6b7b702..5e45b4c 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -869,18 +869,25 @@
private int createDraftSession(String packageName, String installerPackage,
String callerPackageName,
IntentSender statusReceiver, int userId) throws IOException {
+ Computer snapshot = mPm.snapshotComputer();
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.setAppPackageName(packageName);
sessionParams.setAppLabel(
mContext.getString(com.android.internal.R.string.unarchival_session_app_label));
- sessionParams.setAppIcon(
- getArchivedAppIcon(packageName, UserHandle.of(userId), callerPackageName));
+ // The draft session's app icon is based on the current launcher's icon overlay appops mode
+ String launcherPackageName = getCurrentLauncherPackageName(userId);
+ int launcherUid = launcherPackageName != null
+ ? snapshot.getPackageUid(launcherPackageName, 0, userId)
+ : Process.SYSTEM_UID;
+ sessionParams.setAppIcon(getArchivedAppIcon(packageName, UserHandle.of(userId),
+ isOverlayEnabled(launcherUid,
+ launcherPackageName == null ? callerPackageName : launcherPackageName)));
// To make sure SessionInfo::isUnarchival returns true for draft sessions,
// INSTALL_UNARCHIVE is also set.
sessionParams.installFlags = (INSTALL_UNARCHIVE_DRAFT | INSTALL_UNARCHIVE);
- int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
+ int installerUid = snapshot.getPackageUid(installerPackage, 0, userId);
// Handles case of repeated unarchival calls for the same package.
int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
sessionParams,
@@ -926,12 +933,27 @@
/**
* Returns the icon of an archived app. This is the icon of the main activity of the app.
*
- * <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
- * launcher activities, only one of the icons is returned arbitrarily.
+ * <p> In the rare case the app had multiple launcher activities, only one of the icons is
+ * returned arbitrarily.
+ *
+ * <p> By default, the icon will be overlay'd with a cloud icon on top. A launcher app can
+ * disable the cloud overlay via the
+ * {@link LauncherApps.ArchiveCompatibilityParams#setEnableIconOverlay(boolean)} API.
+ * The default launcher's cloud overlay mode determines the cloud overlay status returned by
+ * any other callers. That is, if the current launcher has the cloud overlay disabled, any other
+ * app that fetches the app icon will also get an icon that has the cloud overlay disabled.
+ * This is to prevent style mismatch caused by icons that are fetched by different callers.
*/
@Nullable
public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
String callingPackageName) {
+ return getArchivedAppIcon(packageName, user,
+ isOverlayEnabled(Binder.getCallingUid(), callingPackageName));
+ }
+
+ @Nullable
+ private Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
+ boolean isOverlayEnabled) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(user);
@@ -955,14 +977,19 @@
// In the rare case the archived app defined more than two launcher activities, we choose
// the first one arbitrarily.
Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0));
- if (icon != null && getAppOpsManager().checkOp(
- AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName)
- == MODE_ALLOWED) {
+
+ if (icon != null && isOverlayEnabled) {
icon = includeCloudOverlay(icon);
}
return icon;
}
+ private boolean isOverlayEnabled(int callingUid, String packageName) {
+ return getAppOpsManager().checkOp(
+ AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, packageName)
+ == MODE_ALLOWED;
+ }
+
/**
* This method first checks the ArchiveState for the provided userId and then tries to fallback
* to other users if the current user is not archived.
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 613ebd3..46207c1 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -579,11 +579,11 @@
+ " to user %d on start", userId, displayId, userAssignedToDisplay);
return false;
}
- // Then if was assigned extra
- userAssignedToDisplay = mExtraDisplaysAssignedToUsers.get(userId, USER_NULL);
+ // Then if the display was assigned before
+ userAssignedToDisplay = mExtraDisplaysAssignedToUsers.get(displayId, USER_NULL);
if (userAssignedToDisplay != USER_NULL) {
Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because user %d was already "
- + "assigned that extra display", userId, displayId, userAssignedToDisplay);
+ + "assigned to extra display", userId, displayId, userAssignedToDisplay);
return false;
}
if (DBG) {
diff --git a/services/core/java/com/android/server/power/stats/AmbientDisplayPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/AmbientDisplayPowerStatsProcessor.java
new file mode 100644
index 0000000..a42929f
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AmbientDisplayPowerStatsProcessor.java
@@ -0,0 +1,75 @@
+/*
+ * 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.power.stats;
+
+import android.os.BatteryConsumer;
+import android.os.PersistableBundle;
+
+import com.android.internal.os.PowerStats;
+
+public class AmbientDisplayPowerStatsProcessor extends PowerStatsProcessor {
+ private final PowerStatsLayout mStatsLayout;
+ private final PowerStats.Descriptor mDescriptor;
+ private final long[] mTmpDeviceStats;
+ private PowerStats.Descriptor mScreenPowerStatsDescriptor;
+ private ScreenPowerStatsLayout mScreenPowerStatsLayout;
+ private long[] mTmpScreenStats;
+
+ public AmbientDisplayPowerStatsProcessor() {
+ mStatsLayout = new PowerStatsLayout();
+ mStatsLayout.addDeviceSectionPowerEstimate();
+ PersistableBundle extras = new PersistableBundle();
+ mStatsLayout.toExtras(extras);
+ mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
+ mStatsLayout.getDeviceStatsArrayLength(), null, 0, 0, extras);
+ mTmpDeviceStats = new long[mDescriptor.statsArrayLength];
+ }
+
+ @Override
+ void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+ stats.setPowerStatsDescriptor(mDescriptor);
+
+ PowerComponentAggregatedPowerStats screenStats =
+ stats.getAggregatedPowerStats().getPowerComponentStats(
+ BatteryConsumer.POWER_COMPONENT_SCREEN);
+ if (screenStats == null) {
+ return;
+ }
+
+ if (mScreenPowerStatsDescriptor == null) {
+ mScreenPowerStatsDescriptor = screenStats.getPowerStatsDescriptor();
+ if (mScreenPowerStatsDescriptor == null) {
+ return;
+ }
+
+ mScreenPowerStatsLayout = new ScreenPowerStatsLayout(mScreenPowerStatsDescriptor);
+ mTmpScreenStats = new long[mScreenPowerStatsDescriptor.statsArrayLength];
+ }
+
+ MultiStateStats.States[] deviceStateConfig = screenStats.getConfig().getDeviceStateConfig();
+
+ // Ambient display power estimates have already been calculated by the screen power stats
+ // processor. All that remains to be done is copy the estimates over.
+ MultiStateStats.States.forEachTrackedStateCombination(deviceStateConfig,
+ states -> {
+ screenStats.getDeviceStats(mTmpScreenStats, states);
+ double power =
+ mScreenPowerStatsLayout.getScreenDozePowerEstimate(mTmpScreenStats);
+ mStatsLayout.setDevicePowerEstimate(mTmpDeviceStats, power);
+ stats.setDeviceStats(states, mTmpDeviceStats);
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 4052a64..c4b37c69 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -296,6 +296,7 @@
private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
private int[] mCpuPowerBracketMap;
private final CpuPowerStatsCollector mCpuPowerStatsCollector;
+ private final ScreenPowerStatsCollector mScreenPowerStatsCollector;
private final MobileRadioPowerStatsCollector mMobileRadioPowerStatsCollector;
private final WifiPowerStatsCollector mWifiPowerStatsCollector;
private final BluetoothPowerStatsCollector mBluetoothPowerStatsCollector;
@@ -303,6 +304,54 @@
private final GnssPowerStatsCollector mGnssPowerStatsCollector;
private final CustomEnergyConsumerPowerStatsCollector mCustomEnergyConsumerPowerStatsCollector;
private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray();
+ private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever =
+ new ScreenPowerStatsCollector.ScreenUsageTimeRetriever() {
+
+ @Override
+ public long getScreenOnTimeMs(int display) {
+ synchronized (BatteryStatsImpl.this) {
+ return getDisplayScreenOnTime(display,
+ mClock.elapsedRealtime() * 1000) / 1000;
+ }
+ }
+
+ @Override
+ public long getBrightnessLevelTimeMs(int display, int brightnessLevel) {
+ synchronized (BatteryStatsImpl.this) {
+ return getDisplayScreenBrightnessTime(display, brightnessLevel,
+ mClock.elapsedRealtime() * 1000) / 1000;
+ }
+ }
+
+ @Override
+ public long getScreenDozeTimeMs(int display) {
+ synchronized (BatteryStatsImpl.this) {
+ return getDisplayScreenDozeTime(display,
+ mClock.elapsedRealtime() * 1000) / 1000;
+ }
+ }
+
+ @Override
+ public void retrieveTopActivityTimes(Callback callback) {
+ synchronized (BatteryStatsImpl.this) {
+ long elapsedTimeUs = mClock.elapsedRealtime() * 1000;
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ Uid uid = mUidStats.valueAt(i);
+ long topStateTime = uid.getProcessStateTime(Uid.PROCESS_STATE_TOP,
+ elapsedTimeUs, STATS_SINCE_CHARGED) / 1000;
+ Timer timer = uid.getForegroundActivityTimer();
+ if (timer == null) {
+ callback.onUidTopActivityTime(uid.mUid, topStateTime);
+ } else {
+ long topActivityTime = timer.getTotalTimeLocked(elapsedTimeUs,
+ STATS_SINCE_CHARGED) / 1000;
+ callback.onUidTopActivityTime(uid.mUid, Math.min(topStateTime,
+ topActivityTime));
+ }
+ }
+ }
+ }
+ };
private final WifiPowerStatsCollector.WifiStatsRetriever mWifiStatsRetriever =
new WifiPowerStatsCollector.WifiStatsRetriever() {
@Override
@@ -1966,8 +2015,9 @@
}
private class PowerStatsCollectorInjector implements CpuPowerStatsCollector.Injector,
- MobileRadioPowerStatsCollector.Injector, WifiPowerStatsCollector.Injector,
- BluetoothPowerStatsCollector.Injector, EnergyConsumerPowerStatsCollector.Injector {
+ ScreenPowerStatsCollector.Injector, MobileRadioPowerStatsCollector.Injector,
+ WifiPowerStatsCollector.Injector, BluetoothPowerStatsCollector.Injector,
+ EnergyConsumerPowerStatsCollector.Injector {
private PackageManager mPackageManager;
private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
private NetworkStatsManager mNetworkStatsManager;
@@ -2039,6 +2089,16 @@
}
@Override
+ public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() {
+ return mScreenUsageTimeRetriever;
+ }
+
+ @Override
+ public int getDisplayCount() {
+ return BatteryStatsImpl.this.getDisplayCount();
+ }
+
+ @Override
public Supplier<NetworkStats> getMobileNetworkStatsSupplier() {
return () -> readMobileNetworkStatsLocked(mNetworkStatsManager);
}
@@ -5736,13 +5796,17 @@
maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs);
if (shouldScheduleSync) {
- final int numDisplays = mPerDisplayBatteryStats.length;
- final int[] displayStates = new int[numDisplays];
- for (int i = 0; i < numDisplays; i++) {
- displayStates[i] = mPerDisplayBatteryStats[i].screenState;
+ if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_SCREEN)) {
+ mScreenPowerStatsCollector.schedule();
+ } else {
+ final int numDisplays = mPerDisplayBatteryStats.length;
+ final int[] displayStates = new int[numDisplays];
+ for (int i = 0; i < numDisplays; i++) {
+ displayStates[i] = mPerDisplayBatteryStats[i].screenState;
+ }
+ mExternalSync.scheduleSyncDueToScreenStateChange(externalUpdateFlag,
+ batteryRunning, batteryScreenOffRunning, state, displayStates);
}
- mExternalSync.scheduleSyncDueToScreenStateChange(externalUpdateFlag,
- batteryRunning, batteryScreenOffRunning, state, displayStates);
}
}
@@ -11290,6 +11354,9 @@
mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector);
mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
+ mScreenPowerStatsCollector = new ScreenPowerStatsCollector(mPowerStatsCollectorInjector);
+ mScreenPowerStatsCollector.addConsumer(this::recordPowerStats);
+
mMobileRadioPowerStatsCollector = new MobileRadioPowerStatsCollector(
mPowerStatsCollectorInjector, this::onMobileRadioPowerStatsRetrieved);
mMobileRadioPowerStatsCollector.addConsumer(this::recordPowerStats);
@@ -14750,6 +14817,10 @@
mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU));
mCpuPowerStatsCollector.schedule();
+ mScreenPowerStatsCollector.setEnabled(
+ mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_SCREEN));
+ mScreenPowerStatsCollector.schedule();
+
mMobileRadioPowerStatsCollector.setEnabled(
mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO));
mMobileRadioPowerStatsCollector.schedule();
@@ -14786,6 +14857,8 @@
switch (powerComponent) {
case BatteryConsumer.POWER_COMPONENT_CPU:
return mCpuPowerStatsCollector;
+ case BatteryConsumer.POWER_COMPONENT_SCREEN:
+ return mScreenPowerStatsCollector;
case BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO:
return mMobileRadioPowerStatsCollector;
case BatteryConsumer.POWER_COMPONENT_WIFI:
@@ -16329,6 +16402,7 @@
*/
public void schedulePowerStatsSampleCollection() {
mCpuPowerStatsCollector.forceSchedule();
+ mScreenPowerStatsCollector.forceSchedule();
mMobileRadioPowerStatsCollector.forceSchedule();
mWifiPowerStatsCollector.forceSchedule();
mBluetoothPowerStatsCollector.forceSchedule();
@@ -16351,6 +16425,7 @@
*/
public void dumpStatsSample(PrintWriter pw) {
mCpuPowerStatsCollector.collectAndDump(pw);
+ mScreenPowerStatsCollector.collectAndDump(pw);
mMobileRadioPowerStatsCollector.collectAndDump(pw);
mWifiPowerStatsCollector.collectAndDump(pw);
mBluetoothPowerStatsCollector.collectAndDump(pw);
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index ac68966..a5e4cf5 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -110,8 +110,13 @@
if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_VIDEO)) {
mPowerCalculators.add(new VideoPowerCalculator(mPowerProfile));
}
- mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile));
- mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
+ if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_SCREEN)) {
+ mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile));
+ }
+ if (!mPowerStatsExporterEnabled.get(
+ BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY)) {
+ mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
+ }
mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_ANY)) {
mPowerCalculators.add(new CustomEnergyConsumerPowerCalculator(mPowerProfile));
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 8384b2b..6820197 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -163,15 +163,14 @@
}
}
- void setDeviceStats(@AggregatedPowerStatsConfig.TrackedState int[] states, long[] values) {
+ void setDeviceStats(int[] states, long[] values) {
if (mDeviceStats == null) {
createDeviceStats(0);
}
mDeviceStats.setStats(states, values);
}
- void setUidStats(int uid, @AggregatedPowerStatsConfig.TrackedState int[] states,
- long[] values) {
+ void setUidStats(int uid, int[] states, long[] values) {
UidStats uidStats = getUidStats(uid);
uidStats.stats.setStats(states, values);
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
index dfc8daa..7d7b3c2 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
@@ -220,8 +220,7 @@
}
@Nullable
- public DeviceStateEstimation getDeviceStateEstimate(
- @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ public DeviceStateEstimation getDeviceStateEstimate(int[] stateValues) {
String label = concatLabels(mConfig.getDeviceStateConfig(), stateValues);
for (int i = 0; i < deviceStateEstimations.size(); i++) {
DeviceStateEstimation deviceStateEstimation = this.deviceStateEstimations.get(i);
@@ -233,8 +232,7 @@
}
public CombinedDeviceStateEstimate getCombinedDeviceStateEstimate(
- MultiStateStats.States[] deviceStates,
- @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ MultiStateStats.States[] deviceStates, int[] stateValues) {
String label = concatLabels(deviceStates, stateValues);
for (int i = 0; i < combinedDeviceStateEstimations.size(); i++) {
CombinedDeviceStateEstimate cdse = combinedDeviceStateEstimations.get(i);
@@ -275,12 +273,10 @@
protected static class DeviceStateEstimation {
public final String id;
- @AggregatedPowerStatsConfig.TrackedState
public final int[] stateValues;
public Object intermediates;
- public DeviceStateEstimation(MultiStateStats.States[] config,
- @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ public DeviceStateEstimation(MultiStateStats.States[] config, int[] stateValues) {
id = concatLabels(config, stateValues);
this.stateValues = stateValues;
}
@@ -288,11 +284,12 @@
protected static class CombinedDeviceStateEstimate {
public final String id;
+ public final int[] stateValues;
public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>();
public Object intermediates;
- public CombinedDeviceStateEstimate(MultiStateStats.States[] config,
- @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ public CombinedDeviceStateEstimate(MultiStateStats.States[] config, int[] stateValues) {
+ this.stateValues = Arrays.copyOf(stateValues, stateValues.length);
id = concatLabels(config, stateValues);
}
}
@@ -310,19 +307,16 @@
}
protected static class UidStateProportionalEstimate {
- @AggregatedPowerStatsConfig.TrackedState
public final int[] stateValues;
public Object intermediates;
- protected UidStateProportionalEstimate(
- @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ protected UidStateProportionalEstimate(int[] stateValues) {
this.stateValues = stateValues;
}
}
@NonNull
- private static String concatLabels(MultiStateStats.States[] config,
- @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ private static String concatLabels(MultiStateStats.States[] config, int[] stateValues) {
List<String> labels = new ArrayList<>();
for (int state = 0; state < config.length; state++) {
if (config[state] != null && config[state].isTracked()) {
@@ -334,7 +328,6 @@
return labels.toString();
}
- @AggregatedPowerStatsConfig.TrackedState
private static int[][] getAllTrackedStateCombinations(MultiStateStats.States[] states) {
List<int[]> combinations = new ArrayList<>();
MultiStateStats.States.forEachTrackedStateCombination(states, stateValues -> {
diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java
new file mode 100644
index 0000000..291f289
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java
@@ -0,0 +1,227 @@
+/*
+ * 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.power.stats;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.util.Slog;
+import android.util.SparseLongArray;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerStats;
+
+import java.util.Arrays;
+import java.util.function.IntSupplier;
+
+public class ScreenPowerStatsCollector extends PowerStatsCollector {
+ private static final String TAG = "ScreenPowerStatsCollector";
+
+ interface ScreenUsageTimeRetriever {
+ interface Callback {
+ void onUidTopActivityTime(int uid, long topActivityTimeMs);
+ }
+
+ void retrieveTopActivityTimes(Callback callback);
+
+ long getScreenOnTimeMs(int display);
+ long getBrightnessLevelTimeMs(int display, int brightnessLevel);
+ long getScreenDozeTimeMs(int display);
+ }
+
+ interface Injector {
+ Handler getHandler();
+ Clock getClock();
+ PowerStatsUidResolver getUidResolver();
+ long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
+ ConsumedEnergyRetriever getConsumedEnergyRetriever();
+ IntSupplier getVoltageSupplier();
+ ScreenUsageTimeRetriever getScreenUsageTimeRetriever();
+ int getDisplayCount();
+ }
+
+ private static final long ENERGY_UNSPECIFIED = -1;
+
+ private final Injector mInjector;
+ private boolean mIsInitialized;
+ private ScreenPowerStatsLayout mLayout;
+ private int mDisplayCount;
+ private PowerStats mPowerStats;
+ private ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ private IntSupplier mVoltageSupplier;
+ private ScreenUsageTimeRetriever mScreenUsageTimeRetriever;
+ private int[] mEnergyConsumerIds = new int[0];
+ private long[] mLastConsumedEnergyUws;
+ private int mLastVoltageMv;
+ private boolean mFirstSample = true;
+ private long[] mLastScreenOnTime;
+ private long[][] mLastBrightnessLevelTime;
+ private long[] mLastDozeTime;
+ private final SparseLongArray mLastTopActivityTime = new SparseLongArray();
+ private long mLastCollectionTime;
+
+ ScreenPowerStatsCollector(Injector injector) {
+ super(injector.getHandler(),
+ injector.getPowerStatsCollectionThrottlePeriod(
+ BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_SCREEN)),
+ injector.getUidResolver(), injector.getClock());
+ mInjector = injector;
+ }
+
+ private boolean ensureInitialized() {
+ if (mIsInitialized) {
+ return true;
+ }
+
+ if (!isEnabled()) {
+ return false;
+ }
+
+ mDisplayCount = mInjector.getDisplayCount();
+ mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
+ mVoltageSupplier = mInjector.getVoltageSupplier();
+ mScreenUsageTimeRetriever = mInjector.getScreenUsageTimeRetriever();
+ mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(
+ EnergyConsumerType.DISPLAY);
+ mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length];
+ Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
+
+ mLayout = new ScreenPowerStatsLayout();
+ mLayout.addDeviceScreenUsageDurationSection(mInjector.getDisplayCount());
+ mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length);
+ mLayout.addDeviceSectionUsageDuration();
+ mLayout.addDeviceSectionPowerEstimate();
+ mLayout.addUidTopActivitiyDuration();
+ mLayout.addUidSectionPowerEstimate();
+
+ PersistableBundle extras = new PersistableBundle();
+ mLayout.toExtras(extras);
+ PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor(
+ BatteryConsumer.POWER_COMPONENT_SCREEN, mLayout.getDeviceStatsArrayLength(),
+ null, 0, mLayout.getUidStatsArrayLength(),
+ extras);
+
+ mLastScreenOnTime = new long[mDisplayCount];
+ mLastBrightnessLevelTime = new long[mDisplayCount][BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS];
+ mLastDozeTime = new long[mDisplayCount];
+
+ mPowerStats = new PowerStats(powerStatsDescriptor);
+
+ mIsInitialized = true;
+ return true;
+ }
+
+ @Override
+ protected PowerStats collectStats() {
+ if (!ensureInitialized()) {
+ return null;
+ }
+
+ if (mEnergyConsumerIds.length != 0) {
+ collectEnergyConsumers();
+ }
+
+ for (int display = 0; display < mDisplayCount; display++) {
+ long screenOnTimeMs = mScreenUsageTimeRetriever.getScreenOnTimeMs(display);
+ if (!mFirstSample) {
+ mLayout.setScreenOnDuration(mPowerStats.stats, display,
+ screenOnTimeMs - mLastScreenOnTime[display]);
+ }
+ mLastScreenOnTime[display] = screenOnTimeMs;
+
+ for (int level = 0; level < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; level++) {
+ long brightnessLevelTimeMs =
+ mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(display, level);
+ if (!mFirstSample) {
+ mLayout.setBrightnessLevelDuration(mPowerStats.stats, display, level,
+ brightnessLevelTimeMs - mLastBrightnessLevelTime[display][level]);
+ }
+ mLastBrightnessLevelTime[display][level] = brightnessLevelTimeMs;
+ }
+ long screenDozeTimeMs = mScreenUsageTimeRetriever.getScreenDozeTimeMs(display);
+ if (!mFirstSample) {
+ mLayout.setScreenDozeDuration(mPowerStats.stats, display,
+ screenDozeTimeMs - mLastDozeTime[display]);
+ }
+ mLastDozeTime[display] = screenDozeTimeMs;
+ }
+
+ mPowerStats.uidStats.clear();
+
+ mScreenUsageTimeRetriever.retrieveTopActivityTimes((uid, topActivityTimeMs) -> {
+ long topActivityDuration = topActivityTimeMs - mLastTopActivityTime.get(uid);
+ if (topActivityDuration == 0) {
+ return;
+ }
+ mLastTopActivityTime.put(uid, topActivityTimeMs);
+
+ int mappedUid = mUidResolver.mapUid(uid);
+ long[] uidStats = mPowerStats.uidStats.get(mappedUid);
+ if (uidStats == null) {
+ uidStats = new long[mLayout.getUidStatsArrayLength()];
+ mPowerStats.uidStats.put(mappedUid, uidStats);
+ }
+
+ mLayout.setUidTopActivityDuration(uidStats,
+ mLayout.getUidTopActivityDuration(uidStats) + topActivityDuration);
+ });
+
+ long elapsedRealtime = mClock.elapsedRealtime();
+ mPowerStats.durationMs = elapsedRealtime - mLastCollectionTime;
+ mLastCollectionTime = elapsedRealtime;
+
+ mFirstSample = false;
+
+ return mPowerStats;
+ }
+
+ private void collectEnergyConsumers() {
+ int voltageMv = mVoltageSupplier.getAsInt();
+ if (voltageMv <= 0) {
+ Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
+ + " mV) when querying energy consumers");
+ return;
+ }
+
+ int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
+ mLastVoltageMv = voltageMv;
+
+ long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds);
+ if (energyUws == null) {
+ return;
+ }
+
+ for (int i = energyUws.length - 1; i >= 0; i--) {
+ long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED
+ ? energyUws[i] - mLastConsumedEnergyUws[i] : 0;
+ if (energyDelta < 0) {
+ // Likely, restart of powerstats HAL
+ energyDelta = 0;
+ }
+ mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage));
+ mLastConsumedEnergyUws[i] = energyUws[i];
+ }
+ }
+
+ @Override
+ protected void onUidRemoved(int uid) {
+ mLastTopActivityTime.delete(uid);
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java
new file mode 100644
index 0000000..f134aa8
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java
@@ -0,0 +1,171 @@
+/*
+ * 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.power.stats;
+
+import android.annotation.NonNull;
+import android.os.BatteryStats;
+import android.os.PersistableBundle;
+
+import com.android.internal.os.PowerStats;
+
+/**
+ * Captures the positions and lengths of sections of the stats array, such as time-in-state,
+ * power usage estimates etc.
+ */
+public class ScreenPowerStatsLayout extends PowerStatsLayout {
+ private static final String EXTRA_DEVICE_SCREEN_COUNT = "dsc";
+ private static final String EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION = "dsd";
+ private static final String EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS = "dbd";
+ private static final String EXTRA_DEVICE_DOZE_DURATION_POSITION = "ddd";
+ private static final String EXTRA_DEVICE_DOZE_POWER_POSITION = "ddp";
+ private static final String EXTRA_UID_FOREGROUND_DURATION = "uf";
+
+ private int mDisplayCount;
+ private int mDeviceScreenOnDurationPosition;
+ private int[] mDeviceBrightnessDurationPositions;
+ private int mDeviceScreenDozeDurationPosition;
+ private int mDeviceScreenDozePowerPosition;
+ private int mUidTopActivityTimePosition;
+
+ ScreenPowerStatsLayout() {
+ }
+
+ ScreenPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) {
+ super(descriptor);
+ }
+
+ void addDeviceScreenUsageDurationSection(int displayCount) {
+ mDisplayCount = displayCount;
+ mDeviceScreenOnDurationPosition = addDeviceSection(displayCount, "on");
+ mDeviceBrightnessDurationPositions = new int[BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS];
+ for (int level = 0; level < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; level++) {
+ mDeviceBrightnessDurationPositions[level] =
+ addDeviceSection(displayCount, BatteryStats.SCREEN_BRIGHTNESS_NAMES[level]);
+ }
+ mDeviceScreenDozeDurationPosition = addDeviceSection(displayCount, "doze");
+ }
+
+ @Override
+ public void addDeviceSectionPowerEstimate() {
+ super.addDeviceSectionPowerEstimate();
+ // Used by AmbientDisplayPowerStatsProcessor
+ mDeviceScreenDozePowerPosition = addDeviceSection(1, "doze-power", FLAG_HIDDEN);
+ }
+
+ public int getDisplayCount() {
+ return mDisplayCount;
+ }
+
+ /**
+ * Stores screen-on time for the specified display.
+ */
+ public void setScreenOnDuration(long[] stats, int display, long durationMs) {
+ stats[mDeviceScreenOnDurationPosition + display] = durationMs;
+ }
+
+ /**
+ * Returns screen-on time for the specified display.
+ */
+ public long getScreenOnDuration(long[] stats, int display) {
+ return stats[mDeviceScreenOnDurationPosition + display];
+ }
+
+ /**
+ * Stores time at the specified brightness level for the specified display.
+ */
+ public void setBrightnessLevelDuration(long[] stats, int display, int brightnessLevel,
+ long durationMs) {
+ stats[mDeviceBrightnessDurationPositions[brightnessLevel] + display] = durationMs;
+ }
+
+ /**
+ * Returns time at the specified brightness level for the specified display.
+ */
+ public long getBrightnessLevelDuration(long[] stats, int display, int brightnessLevel) {
+ return stats[mDeviceBrightnessDurationPositions[brightnessLevel] + display];
+ }
+
+ /**
+ * Stores time in the doze (ambient) state for the specified display.
+ */
+ public void setScreenDozeDuration(long[] stats, int display, long durationMs) {
+ stats[mDeviceScreenDozeDurationPosition + display] = durationMs;
+ }
+
+ /**
+ * Retrieves time in the doze (ambient) state for the specified display.
+ */
+ public long getScreenDozeDuration(long[] stats, int display) {
+ return stats[mDeviceScreenDozeDurationPosition + display];
+ }
+
+ /**
+ * Stores estimated power in the doze (ambient) state.
+ */
+ public void setScreenDozePowerEstimate(long[] stats, double power) {
+ stats[mDeviceScreenDozePowerPosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
+ }
+
+ /**
+ * Retrieves estimated power in the doze (ambient) state.
+ */
+ public double getScreenDozePowerEstimate(long[] stats) {
+ return stats[mDeviceScreenDozePowerPosition] / MILLI_TO_NANO_MULTIPLIER;
+ }
+
+ void addUidTopActivitiyDuration() {
+ mUidTopActivityTimePosition = addUidSection(1, "top");
+ }
+
+ /**
+ * Stores time the UID spent in the TOP state.
+ */
+ public void setUidTopActivityDuration(long[] stats, long durationMs) {
+ stats[mUidTopActivityTimePosition] = durationMs;
+ }
+
+ /**
+ * Returns time the UID spent in the TOP state.
+ */
+ public long getUidTopActivityDuration(long[] stats) {
+ return stats[mUidTopActivityTimePosition];
+ }
+
+ @Override
+ public void toExtras(PersistableBundle extras) {
+ super.toExtras(extras);
+ extras.putInt(EXTRA_DEVICE_SCREEN_COUNT, mDisplayCount);
+ extras.putInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION, mDeviceScreenOnDurationPosition);
+ extras.putIntArray(EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS,
+ mDeviceBrightnessDurationPositions);
+ extras.putInt(EXTRA_DEVICE_DOZE_DURATION_POSITION, mDeviceScreenDozeDurationPosition);
+ extras.putInt(EXTRA_DEVICE_DOZE_POWER_POSITION, mDeviceScreenDozePowerPosition);
+ extras.putInt(EXTRA_UID_FOREGROUND_DURATION, mUidTopActivityTimePosition);
+ }
+
+ @Override
+ public void fromExtras(PersistableBundle extras) {
+ super.fromExtras(extras);
+ mDisplayCount = extras.getInt(EXTRA_DEVICE_SCREEN_COUNT, 1);
+ mDeviceScreenOnDurationPosition = extras.getInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION);
+ mDeviceBrightnessDurationPositions = extras.getIntArray(
+ EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS);
+ mDeviceScreenDozeDurationPosition = extras.getInt(EXTRA_DEVICE_DOZE_DURATION_POSITION);
+ mDeviceScreenDozePowerPosition = extras.getInt(EXTRA_DEVICE_DOZE_POWER_POSITION);
+ mUidTopActivityTimePosition = extras.getInt(EXTRA_UID_FOREGROUND_DURATION);
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
new file mode 100644
index 0000000..e203e4a
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
@@ -0,0 +1,238 @@
+/*
+ * 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.power.stats;
+
+import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
+
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import android.os.BatteryStats;
+import android.util.Slog;
+
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ScreenPowerStatsProcessor extends PowerStatsProcessor {
+ private static final String TAG = "ScreenPowerStatsProcessor";
+ private final int mDisplayCount;
+ private final UsageBasedPowerEstimator[] mScreenOnPowerEstimators;
+ private final UsageBasedPowerEstimator[] mScreenDozePowerEstimators;
+ private final UsageBasedPowerEstimator[][] mScreenBrightnessLevelPowerEstimators;
+ private PowerStats.Descriptor mLastUsedDescriptor;
+ private ScreenPowerStatsLayout mStatsLayout;
+ private PowerEstimationPlan mPlan;
+ private long[] mTmpDeviceStatsArray;
+ private long[] mTmpUidStatsArray;
+
+ private static class Intermediates {
+ public double power;
+ }
+
+ public ScreenPowerStatsProcessor(PowerProfile powerProfile) {
+ mDisplayCount = powerProfile.getNumDisplays();
+ mScreenOnPowerEstimators = new UsageBasedPowerEstimator[mDisplayCount];
+ mScreenDozePowerEstimators = new UsageBasedPowerEstimator[mDisplayCount];
+ mScreenBrightnessLevelPowerEstimators = new UsageBasedPowerEstimator[mDisplayCount][];
+ for (int display = 0; display < mDisplayCount; display++) {
+ mScreenOnPowerEstimators[display] = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, display));
+
+ double averagePowerFullBrightness = powerProfile.getAveragePowerForOrdinal(
+ POWER_GROUP_DISPLAY_SCREEN_FULL, display);
+ mScreenBrightnessLevelPowerEstimators[display] =
+ new UsageBasedPowerEstimator[BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS];
+ for (int bin = 0; bin < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; bin++) {
+ // For example, if the number of bins is 3, the corresponding averages
+ // are calculated as 0.5 * full, 1.5 * full, 2.5 * full
+ final double binPowerMah = averagePowerFullBrightness * (bin + 0.5)
+ / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
+ mScreenBrightnessLevelPowerEstimators[display][bin] =
+ new UsageBasedPowerEstimator(binPowerMah);
+ }
+
+ mScreenDozePowerEstimators[display] = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, display));
+ }
+ }
+
+ private boolean unpackPowerStatsDescriptor(PowerStats.Descriptor descriptor) {
+ if (descriptor == null) {
+ return false;
+ }
+
+ if (descriptor.equals(mLastUsedDescriptor)) {
+ return true;
+ }
+
+ mLastUsedDescriptor = descriptor;
+ mStatsLayout = new ScreenPowerStatsLayout(descriptor);
+ if (mStatsLayout.getDisplayCount() != mDisplayCount) {
+ Slog.e(TAG, "Incompatible number of displays: " + mStatsLayout.getDisplayCount()
+ + ", expected: " + mDisplayCount);
+ return false;
+ }
+
+ mTmpDeviceStatsArray = new long[descriptor.statsArrayLength];
+ mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength];
+ return true;
+ }
+
+ @Override
+ void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+ if (!unpackPowerStatsDescriptor(stats.getPowerStatsDescriptor())) {
+ return;
+ }
+
+ if (mPlan == null) {
+ mPlan = new PowerEstimationPlan(stats.getConfig());
+ }
+
+ computeDevicePowerEstimates(stats);
+ combineDeviceStateEstimates();
+
+ List<Integer> uids = new ArrayList<>();
+ stats.collectUids(uids);
+
+ if (!uids.isEmpty()) {
+ computeUidPowerEstimates(stats, uids);
+ }
+ }
+
+ private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) {
+ for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) {
+ DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i);
+ if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) {
+ continue;
+ }
+
+ if (estimation.stateValues[STATE_SCREEN] == SCREEN_STATE_ON) {
+ double power;
+ if (mStatsLayout.getEnergyConsumerCount() > 0) {
+ power = uCtoMah(mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, 0));
+ } else {
+ power = 0;
+ for (int display = 0; display < mStatsLayout.getDisplayCount(); display++) {
+ power += computeDisplayPower(mTmpDeviceStatsArray, display);
+ }
+ }
+ mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power);
+ Intermediates intermediates = new Intermediates();
+ intermediates.power = power;
+ estimation.intermediates = intermediates;
+ } else {
+ double power = 0;
+ if (mStatsLayout.getEnergyConsumerCount() > 0) {
+ power = uCtoMah(mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, 0));
+ } else {
+ for (int display = 0; display < mStatsLayout.getDisplayCount(); display++) {
+ power += mScreenDozePowerEstimators[display].calculatePower(
+ mStatsLayout.getScreenDozeDuration(mTmpDeviceStatsArray, display));
+ }
+ }
+ mStatsLayout.setScreenDozePowerEstimate(mTmpDeviceStatsArray, power);
+ }
+
+ stats.setDeviceStats(estimation.stateValues, mTmpDeviceStatsArray);
+ }
+ }
+
+ private double computeDisplayPower(long[] stats, int display) {
+ double power = mScreenOnPowerEstimators[display]
+ .calculatePower(mStatsLayout.getScreenOnDuration(stats, display));
+ for (int bin = 0; bin < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; bin++) {
+ power += mScreenBrightnessLevelPowerEstimators[display][bin]
+ .calculatePower(mStatsLayout.getBrightnessLevelDuration(stats, display, bin));
+ }
+ return power;
+ }
+
+ /**
+ * Combine power estimates before distributing them proportionally to UIDs.
+ */
+ private void combineDeviceStateEstimates() {
+ for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) {
+ CombinedDeviceStateEstimate cdse = mPlan.combinedDeviceStateEstimations.get(i);
+ List<DeviceStateEstimation> deviceStateEstimations = cdse.deviceStateEstimations;
+ double power = 0;
+ for (int j = deviceStateEstimations.size() - 1; j >= 0; j--) {
+ DeviceStateEstimation dse = deviceStateEstimations.get(j);
+ Intermediates intermediates = (Intermediates) dse.intermediates;
+ if (intermediates != null) {
+ power += intermediates.power;
+ }
+ }
+ if (power != 0) {
+ Intermediates cdseIntermediates = new Intermediates();
+ cdseIntermediates.power = power;
+ cdse.intermediates = cdseIntermediates;
+ }
+ }
+ }
+
+ private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats,
+ List<Integer> uids) {
+ int[] uidStateValues = new int[stats.getConfig().getUidStateConfig().length];
+ uidStateValues[STATE_SCREEN] = SCREEN_STATE_ON;
+ uidStateValues[STATE_PROCESS_STATE] = PROCESS_STATE_ANY;
+
+ for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) {
+ UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i);
+ Intermediates intermediates =
+ (Intermediates) uidStateEstimate.combinedDeviceStateEstimate.intermediates;
+ int[] deviceStateValues = uidStateEstimate.combinedDeviceStateEstimate
+ .stateValues;
+ if (deviceStateValues[STATE_SCREEN] != SCREEN_STATE_ON
+ || intermediates == null) {
+ continue;
+ }
+
+ uidStateValues[STATE_POWER] = deviceStateValues[STATE_POWER];
+
+ long totalTopActivityDuration = 0;
+ for (int j = uids.size() - 1; j >= 0; j--) {
+ int uid = uids.get(j);
+ if (stats.getUidStats(mTmpUidStatsArray, uid, uidStateValues)) {
+ totalTopActivityDuration +=
+ mStatsLayout.getUidTopActivityDuration(mTmpUidStatsArray);
+ }
+ }
+
+ if (totalTopActivityDuration == 0) {
+ return;
+ }
+
+ for (int j = uids.size() - 1; j >= 0; j--) {
+ int uid = uids.get(j);
+ if (stats.getUidStats(mTmpUidStatsArray, uid, uidStateValues)) {
+ long duration = mStatsLayout.getUidTopActivityDuration(mTmpUidStatsArray);
+ double power = intermediates.power * duration / totalTopActivityDuration;
+ mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+ stats.setUidStats(uid, uidStateValues, mTmpUidStatsArray);
+ }
+ }
+ }
+ }
+}
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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 72c7be3..ba2594a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3844,6 +3844,7 @@
pw.print(" mPadding="); pw.println(wpSize.mPadding);
});
pw.print(" mCropHint="); pw.println(wallpaper.cropHint);
+ if (multiCrop()) pw.print(" mCropHints="); pw.println(wallpaper.mCropHints);
pw.print(" mName="); pw.println(wallpaper.name);
pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup);
pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent);
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index 09de01e..68f3738 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -43,7 +43,6 @@
import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.WindowInsets;
-import android.view.WindowInsetsController;
import android.view.WindowManager;
import android.window.ScreenCapture;
import android.window.SnapshotDrawerUtils;
@@ -137,7 +136,6 @@
}
abstract ActivityRecord getTopActivity(TYPE source);
- abstract WindowState getTopFullscreenWindow(TYPE source);
abstract ActivityManager.TaskDescription getTaskDescription(TYPE source);
/**
* Find the window for a given task to take a snapshot. Top child of the task is usually the one
@@ -331,7 +329,7 @@
builder.setPixelFormat(pixelFormat);
builder.setIsTranslucent(isTranslucent);
builder.setWindowingMode(source.getWindowingMode());
- builder.setAppearance(getAppearance(source));
+ builder.setAppearance(mainWindow.mAttrs.insetsFlags.appearance);
final Configuration taskConfig = activity.getTask().getConfiguration();
final int displayRotation = taskConfig.windowConfiguration.getDisplayRotation();
@@ -450,7 +448,7 @@
mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
contentInsets, letterboxInsets, false /* isLowResolution */,
false /* isRealSnapshot */, source.getWindowingMode(),
- getAppearance(source), false /* isTranslucent */, false /* hasImeSurface */);
+ attrs.insetsFlags.appearance, false /* isTranslucent */, false /* hasImeSurface */);
return validateSnapshot(taskSnapshot);
}
@@ -460,19 +458,6 @@
}
/**
- * @return The {@link WindowInsetsController.Appearance} flags for the top main app window in
- * the given {@param TYPE}.
- */
- @WindowInsetsController.Appearance
- private int getAppearance(TYPE source) {
- final WindowState topFullscreenWindow = getTopFullscreenWindow(source);
- if (topFullscreenWindow != null) {
- return topFullscreenWindow.mAttrs.insetsFlags.appearance;
- }
- return 0;
- }
-
- /**
* Called when an {@link ActivityRecord} has been removed.
*/
void onAppRemoved(ActivityRecord activity) {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index a8dcaa8..023dd79 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1124,8 +1124,8 @@
}
try {
- mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(),
- EnterPipRequestedItem.obtain(r.token));
+ final EnterPipRequestedItem item = new EnterPipRequestedItem(r.token);
+ mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(), item);
return true;
} catch (Exception e) {
Slog.w(TAG, "Failed to send enter pip requested item: "
@@ -1140,8 +1140,8 @@
void onPictureInPictureUiStateChanged(@NonNull ActivityRecord r,
PictureInPictureUiState pipState) {
try {
- mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(),
- PipStateTransactionItem.obtain(r.token, pipState));
+ final PipStateTransactionItem item = new PipStateTransactionItem(r.token, pipState);
+ mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(), item);
} catch (Exception e) {
Slog.w(TAG, "Failed to send pip state transaction item: "
+ r.intent.getComponent(), e);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 072b1c7..7d70ea1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -495,7 +495,7 @@
final String launchedFromPackage; // always the package who started the activity.
@Nullable
final String launchedFromFeatureId; // always the feature in launchedFromPackage
- private final int mLaunchSourceType; // original launch source type
+ int mLaunchSourceType; // latest launch source type
final Intent intent; // the original intent that generated us
final String shortComponentName; // the short component name of the intent
final String resolvedType; // as per original caller;
@@ -1467,8 +1467,9 @@
+ "display, activityRecord=%s, displayId=%d, config=%s", this, displayId,
config);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- MoveToDisplayItem.obtain(token, displayId, config, activityWindowInfo));
+ final MoveToDisplayItem item =
+ new MoveToDisplayItem(token, displayId, config, activityWindowInfo);
+ mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
} catch (RemoteException e) {
// If process died, whatever.
}
@@ -1507,8 +1508,9 @@
ProtoLog.v(WM_DEBUG_STATES, "Sending position change to %s, onTop: %b",
this, onTop);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- TopResumedActivityChangeItem.obtain(token, onTop));
+ final TopResumedActivityChangeItem item =
+ new TopResumedActivityChangeItem(token, onTop);
+ mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
} catch (RemoteException e) {
// If process died, whatever.
Slog.w(TAG, "Failed to send top-resumed=" + onTop + " to " + this, e);
@@ -2449,6 +2451,10 @@
return mLaunchSourceType == type;
}
+ void updateLaunchSourceType(int launchFromUid, WindowProcessController caller) {
+ mLaunchSourceType = determineLaunchSourceType(launchFromUid, caller);
+ }
+
private int determineLaunchSourceType(int launchFromUid, WindowProcessController caller) {
if (launchFromUid == Process.SYSTEM_UID || launchFromUid == Process.ROOT_UID) {
return LAUNCH_SOURCE_TYPE_SYSTEM;
@@ -2811,9 +2817,9 @@
}
try {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- TransferSplashScreenViewStateItem.obtain(token, parcelable,
- windowAnimationLeash));
+ final TransferSplashScreenViewStateItem item =
+ new TransferSplashScreenViewStateItem(token, parcelable, windowAnimationLeash);
+ mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
scheduleTransferSplashScreenTimeout();
} catch (Exception e) {
Slog.w(TAG, "onCopySplashScreenComplete fail: " + this);
@@ -4288,7 +4294,8 @@
}
void finishRelaunching() {
- mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(false);
+ mAppCompatController.getAppCompatOrientationOverrides()
+ .setRelaunchingAfterRequestedOrientationChanged(false);
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this);
if (mPendingRelaunchCount > 0) {
@@ -4994,8 +5001,8 @@
try {
final ArrayList<ResultInfo> list = new ArrayList<>();
list.add(new ResultInfo(resultWho, requestCode, resultCode, data, callerToken));
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- ActivityResultItem.obtain(token, list));
+ final ActivityResultItem item = new ActivityResultItem(token, list);
+ mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
return;
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending result to " + this, e);
@@ -5006,9 +5013,9 @@
if (forceSendForMediaProjection && attachedToProcess() && isState(STARTED, PAUSING, PAUSED,
STOPPING, STOPPED)) {
// Build result to be returned immediately.
- final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
- token, List.of(new ResultInfo(resultWho, requestCode, resultCode, data,
- callerToken)));
+ final List<ResultInfo> infos = List.of(
+ new ResultInfo(resultWho, requestCode, resultCode, data, callerToken));
+ final ActivityResultItem activityResultItem = new ActivityResultItem(token, infos);
// When the activity result is delivered, the activity will transition to RESUMED.
// Since the activity is only resumed so the result can be immediately delivered,
// return it to its original lifecycle state.
@@ -5112,8 +5119,9 @@
// Making sure the client state is RESUMED after transaction completed and doing
// so only if activity is currently RESUMED. Otherwise, client may have extra
// life-cycle calls to RESUMED (and PAUSED later).
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- NewIntentItem.obtain(token, ar, mState == RESUMED));
+ final NewIntentItem item =
+ new NewIntentItem(token, ar, mState == RESUMED /* resume */);
+ mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
unsent = false;
} catch (RemoteException e) {
Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
@@ -8178,7 +8186,8 @@
mLastReportedConfiguration.getMergedConfiguration())) {
ensureActivityConfiguration(false /* ignoreVisibility */);
if (mPendingRelaunchCount > originalRelaunchingCount) {
- mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(true);
+ mAppCompatController.getAppCompatOrientationOverrides()
+ .setRelaunchingAfterRequestedOrientationChanged(true);
}
if (mTransitionController.inPlayingTransition(this)) {
mTransitionController.mValidateActivityCompat.add(this);
@@ -8394,7 +8403,8 @@
*/
@ActivityInfo.SizeChangesSupportMode
private int supportsSizeChanges() {
- if (mLetterboxUiController.shouldOverrideForceNonResizeApp()) {
+ if (mAppCompatController.getAppCompatResizeOverrides()
+ .shouldOverrideForceNonResizeApp()) {
return SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
}
@@ -8402,7 +8412,8 @@
return SIZE_CHANGES_SUPPORTED_METADATA;
}
- if (mLetterboxUiController.shouldOverrideForceResizeApp()) {
+ if (mAppCompatController.getAppCompatResizeOverrides()
+ .shouldOverrideForceResizeApp()) {
return SIZE_CHANGES_SUPPORTED_OVERRIDE;
}
@@ -9991,7 +10002,7 @@
try {
ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
(andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
- final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(token,
+ final ClientTransactionItem callbackItem = new ActivityRelaunchItem(token,
pendingResults, pendingNewIntents, configChangeFlags,
new MergedConfiguration(getProcessGlobalConfiguration(),
getMergedOverrideConfiguration()),
@@ -10485,7 +10496,7 @@
mAppCompatController.getAppCompatOrientationOverrides()
.shouldIgnoreOrientationRequestLoop());
proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
- mLetterboxUiController.shouldOverrideForceResizeApp());
+ mAppCompatController.getAppCompatResizeOverrides().shouldOverrideForceResizeApp());
proto.write(SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS,
mAppCompatController.getAppCompatAspectRatioOverrides()
.shouldEnableUserAspectRatioSettings());
@@ -10887,12 +10898,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/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
index c02501f..dcc325e 100644
--- a/services/core/java/com/android/server/wm/ActivityRefresher.java
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -84,8 +84,8 @@
ProtoLog.v(WM_DEBUG_STATES,
"Refreshing activity for freeform camera compatibility treatment, "
+ "activityRecord=%s", activity);
- final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
- activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
+ final RefreshCallbackItem refreshCallbackItem =
+ new RefreshCallbackItem(activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
try {
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 48bc813..aa63393 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -590,10 +590,6 @@
return activity;
}
- WindowState getTopFullscreenWindow(ActivityRecord activity) {
- return activity.findMainWindow();
- }
-
@Override
ActivityManager.TaskDescription getTaskDescription(ActivityRecord object) {
return object.taskDescription;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5bfe9d7..c89f3a3 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1793,6 +1793,9 @@
activity.destroyIfPossible("Removes redundant singleInstance");
}
}
+ if (mLastStartActivityRecord != null) {
+ targetTaskTop.mLaunchSourceType = mLastStartActivityRecord.mLaunchSourceType;
+ }
targetTaskTop.mTransitionController.collect(targetTaskTop);
recordTransientLaunchIfNeeded(targetTaskTop);
// Recycle the target task for this launch.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a84598d..1c14c5d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5522,7 +5522,7 @@
final int procCount = procs.size();
for (int i = 0; i < procCount; i++) {
final int procUid = procs.keyAt(i);
- if (UserHandle.isApp(procUid) || !UserHandle.isSameUser(procUid, uid)) {
+ if (!UserHandle.isCore(procUid) || !UserHandle.isSameUser(procUid, uid)) {
// Don't use an app process or different user process for system component.
continue;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index bc7ed65..afdbc0a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -929,7 +929,7 @@
final boolean isTransitionForward = r.isTransitionForward();
final IBinder fragmentToken = r.getTaskFragment().getFragmentToken();
final int deviceId = getDeviceIdForDisplayId(r.getDisplayId());
- final LaunchActivityItem launchActivityItem = LaunchActivityItem.obtain(r.token,
+ final LaunchActivityItem launchActivityItem = new LaunchActivityItem(r.token,
r.intent, System.identityHashCode(r), r.info,
procConfig, overrideConfig, deviceId,
r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
@@ -2801,6 +2801,13 @@
targetActivity, activityOptions);
}
+ if (callingPid > 0) {
+ final WindowProcessController wpc = mService.mProcessMap
+ .getProcess(callingPid);
+ if (wpc != null) {
+ targetActivity.updateLaunchSourceType(callingUid, wpc);
+ }
+ }
mService.getActivityStartController().postStartActivityProcessingForLastStarter(
task.getTopNonFinishingActivity(), ActivityManager.START_TASK_TO_FRONT,
task.getRootTask());
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 05d4c82..25cb134 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -36,6 +36,7 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
import android.annotation.NonNull;
import android.content.pm.PackageManager;
@@ -115,7 +116,7 @@
*/
boolean shouldOverrideMinAspectRatio() {
return mAllowMinAspectRatioOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
- isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO));
+ isChangeEnabled(mActivityRecord, OVERRIDE_MIN_ASPECT_RATIO));
}
/**
@@ -154,7 +155,7 @@
}
boolean isSystemOverrideToFullscreenEnabled() {
- return isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER)
+ return isChangeEnabled(mActivityRecord, OVERRIDE_ANY_ORIENTATION_TO_USER)
&& !mAllowOrientationOverrideOptProp.isFalse()
&& (mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_UNSET
|| mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN);
@@ -302,10 +303,6 @@
private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
}
- private boolean isCompatChangeEnabled(long overrideChangeId) {
- return mActivityRecord.info.isChangeEnabled(overrideChangeId);
- }
-
private Resources getResources() {
return mActivityRecord.mWmService.mContext.getResources();
}
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
index 93a8663..aeaaffd 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -30,6 +30,7 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
import android.annotation.NonNull;
import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
@@ -99,7 +100,8 @@
boolean shouldOverrideMinAspectRatioForCamera() {
return isCameraActive() && mAllowMinAspectRatioOverrideOptProp
.shouldEnableWithOptInOverrideAndOptOutProperty(
- isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
+ isChangeEnabled(mActivityRecord,
+ OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
}
/**
@@ -115,7 +117,7 @@
*/
boolean shouldRefreshActivityForCameraCompat() {
return mCameraCompatAllowRefreshOptProp.shouldEnableWithOptOutOverrideAndProperty(
- isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH));
+ isChangeEnabled(mActivityRecord, OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH));
}
/**
@@ -134,7 +136,7 @@
*/
boolean shouldRefreshActivityViaPauseForCameraCompat() {
return mCameraCompatEnableRefreshViaPauseOptProp.shouldEnableWithOverrideAndProperty(
- isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE));
+ isChangeEnabled(mActivityRecord, OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE));
}
/**
@@ -150,7 +152,7 @@
*/
boolean shouldForceRotateForCameraCompat() {
return mCameraCompatAllowForceRotationOptProp.shouldEnableWithOptOutOverrideAndProperty(
- isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION));
+ isChangeEnabled(mActivityRecord, OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION));
}
/**
@@ -168,7 +170,7 @@
* </ul>
*/
boolean shouldApplyFreeformTreatmentForCameraCompat() {
- return Flags.cameraCompatForFreeform() && !isCompatChangeEnabled(
+ return Flags.cameraCompatForFreeform() && !isChangeEnabled(mActivityRecord,
OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
}
@@ -191,7 +193,7 @@
}
boolean isOverrideOrientationOnlyForCameraEnabled() {
- return isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
+ return isChangeEnabled(mActivityRecord, OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
}
/**
@@ -227,10 +229,6 @@
mAppCompatCameraOverridesState.mFreeformCameraCompatMode = freeformCameraCompatMode;
}
- private boolean isCompatChangeEnabled(long overrideChangeId) {
- return mActivityRecord.info.isChangeEnabled(overrideChangeId);
- }
-
static class AppCompatCameraOverridesState {
// Whether activity "refresh" was requested but not finished in
// ActivityRecord#activityResumedLocked following the camera compat force rotation in
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index f9e2507..54223b6 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -87,6 +87,11 @@
return mAppCompatOverrides.getAppCompatAspectRatioOverrides();
}
+ @NonNull
+ AppCompatResizeOverrides getAppCompatResizeOverrides() {
+ return mAppCompatOverrides.getAppCompatResizeOverrides();
+ }
+
@Nullable
AppCompatCameraPolicy getAppCompatCameraPolicy() {
if (mActivityRecord.mDisplayContent != null) {
@@ -94,4 +99,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/AppCompatOrientationOverrides.java b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
index 0adf825..bd01351 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
@@ -20,14 +20,20 @@
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
+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_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.AppCompatUtils.asLazy;
+import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
import android.annotation.NonNull;
@@ -54,6 +60,10 @@
private final OptPropFactory.OptProp mIgnoreRequestedOrientationOptProp;
@NonNull
private final OptPropFactory.OptProp mAllowIgnoringOrientationRequestWhenLoopDetectedOptProp;
+ @NonNull
+ private final OptPropFactory.OptProp mAllowOrientationOverrideOptProp;
+ @NonNull
+ private final OptPropFactory.OptProp mAllowDisplayOrientationOverrideOptProp;
@NonNull
final OrientationOverridesState mOrientationOverridesState;
@@ -74,6 +84,17 @@
mAllowIgnoringOrientationRequestWhenLoopDetectedOptProp = optPropBuilder.create(
PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED,
isPolicyForIgnoringRequestedOrientationEnabled);
+ mAllowOrientationOverrideOptProp = optPropBuilder.create(
+ PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE);
+ mAllowDisplayOrientationOverrideOptProp = optPropBuilder.create(
+ PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE,
+ () -> mActivityRecord.mDisplayContent != null
+ && mActivityRecord.getTask() != null
+ && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()
+ && !mActivityRecord.getTask().inMultiWindowMode()
+ && mActivityRecord.mDisplayContent.getNaturalOrientation()
+ == ORIENTATION_LANDSCAPE
+ );
}
boolean shouldEnableIgnoreOrientationRequest() {
@@ -81,6 +102,10 @@
isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION));
}
+ boolean isOverrideRespectRequestedOrientationEnabled() {
+ return isChangeEnabled(mActivityRecord, OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
+ }
+
/**
* Whether an app is calling {@link android.app.Activity#setRequestedOrientation}
* in a loop and orientation request should be ignored.
@@ -113,6 +138,26 @@
}
/**
+ * Whether should fix display orientation to landscape natural orientation when a task is
+ * fullscreen and the display is ignoring orientation requests.
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Opt-out component property isn't enabled
+ * <li>Opt-in per-app override is enabled
+ * <li>Task is in fullscreen.
+ * <li>{@link DisplayContent#getIgnoreOrientationRequest} is enabled
+ * <li>Natural orientation of the display is landscape.
+ * </ul>
+ */
+ boolean shouldUseDisplayLandscapeNaturalOrientation() {
+ return mAllowDisplayOrientationOverrideOptProp
+ .shouldEnableWithOptInOverrideAndOptOutProperty(
+ isChangeEnabled(mActivityRecord,
+ OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION));
+ }
+
+ /**
* Sets whether an activity is relaunching after the app has called {@link
* android.app.Activity#setRequestedOrientation}.
*/
@@ -125,6 +170,10 @@
return mOrientationOverridesState.mIsRelaunchingAfterRequestedOrientationChanged;
}
+ boolean isAllowOrientationOverrideOptOut() {
+ return mAllowOrientationOverrideOptProp.isFalse();
+ }
+
@VisibleForTesting
int getSetOrientationRequestCounter() {
return mOrientationOverridesState.mSetOrientationRequestCounter;
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 17f0d97..c5506de 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -86,7 +86,8 @@
return SCREEN_ORIENTATION_PORTRAIT;
}
- if (mAppCompatOverrides.isAllowOrientationOverrideOptOut()) {
+ if (mAppCompatOverrides.getAppCompatOrientationOverrides()
+ .isAllowOrientationOverrideOptOut()) {
return candidate;
}
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index b611ba9..4450011 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -16,20 +16,6 @@
package com.android.server.wm;
-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;
import com.android.server.wm.utils.OptPropFactory;
@@ -39,64 +25,32 @@
*/
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;
- @NonNull
- private final OptPropFactory.OptProp mAllowForceResizeOverrideOptProp;
@NonNull
private final AppCompatOrientationOverrides mAppCompatOrientationOverrides;
@NonNull
private final AppCompatCameraOverrides mAppCompatCameraOverrides;
@NonNull
private final AppCompatAspectRatioOverrides mAppCompatAspectRatioOverrides;
+ @NonNull
+ private final AppCompatFocusOverrides mAppCompatFocusOverrides;
+ @NonNull
+ private final AppCompatResizeOverrides mAppCompatResizeOverrides;
AppCompatOverrides(@NonNull ActivityRecord activityRecord,
@NonNull AppCompatConfiguration appCompatConfiguration,
@NonNull OptPropFactory optPropBuilder) {
- mAppCompatConfiguration = appCompatConfiguration;
- mActivityRecord = activityRecord;
-
- mAppCompatCameraOverrides = new AppCompatCameraOverrides(mActivityRecord,
- mAppCompatConfiguration, optPropBuilder);
- mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(mActivityRecord,
- mAppCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
+ mAppCompatCameraOverrides = new AppCompatCameraOverrides(activityRecord,
+ appCompatConfiguration, optPropBuilder);
+ mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(activityRecord,
+ 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);
-
-
- mAllowOrientationOverrideOptProp = optPropBuilder.create(
- PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE);
-
- mAllowDisplayOrientationOverrideOptProp = optPropBuilder.create(
- PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE,
- () -> mActivityRecord.mDisplayContent != null
- && mActivityRecord.getTask() != null
- && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()
- && !mActivityRecord.getTask().inMultiWindowMode()
- && mActivityRecord.mDisplayContent.getNaturalOrientation()
- == ORIENTATION_LANDSCAPE
- );
-
- mAllowForceResizeOverrideOptProp = optPropBuilder.create(
- PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord,
+ appCompatConfiguration, optPropBuilder);
+ mAppCompatResizeOverrides = new AppCompatResizeOverrides(activityRecord, optPropBuilder);
}
@NonNull
@@ -114,83 +68,13 @@
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() {
- return mAllowOrientationOverrideOptProp.isFalse();
- }
-
- boolean isOverrideRespectRequestedOrientationEnabled() {
- return isCompatChangeEnabled(OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
- }
-
- /**
- * Whether should fix display orientation to landscape natural orientation when a task is
- * fullscreen and the display is ignoring orientation requests.
- *
- * <p>This treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Opt-in per-app override is enabled
- * <li>Task is in fullscreen.
- * <li>{@link DisplayContent#getIgnoreOrientationRequest} is enabled
- * <li>Natural orientation of the display is landscape.
- * </ul>
- */
- boolean shouldUseDisplayLandscapeNaturalOrientation() {
- return mAllowDisplayOrientationOverrideOptProp
- .shouldEnableWithOptInOverrideAndOptOutProperty(
- isCompatChangeEnabled(OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION));
- }
-
- /**
- * 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
- * mode when the window container resizes, such as display size change or screen rotation.
- *
- * <p>This method returns {@code true} when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Per-app override is enabled
- * </ul>
- */
- boolean shouldOverrideForceResizeApp() {
- return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
- isCompatChangeEnabled(FORCE_RESIZE_APP));
- }
-
- /**
- * Whether we should apply the force non resize per-app override. When this override is applied
- * it forces the packages it is applied to to be non-resizable.
- *
- * <p>This method returns {@code true} when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Per-app override is enabled
- * </ul>
- */
- boolean shouldOverrideForceNonResizeApp() {
- return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
- isCompatChangeEnabled(FORCE_NON_RESIZE_APP));
- }
-
- private boolean isCompatChangeEnabled(long overrideChangeId) {
- return mActivityRecord.info.isChangeEnabled(overrideChangeId);
+ @NonNull
+ AppCompatResizeOverrides getAppCompatResizeOverrides() {
+ return mAppCompatResizeOverrides;
}
}
diff --git a/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java b/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java
new file mode 100644
index 0000000..60c1825
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java
@@ -0,0 +1,78 @@
+/*
+ * 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.FORCE_NON_RESIZE_APP;
+import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+
+import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
+
+import android.annotation.NonNull;
+
+import com.android.server.wm.utils.OptPropFactory;
+
+/**
+ * Encapsulate app compat logic about resizability.
+ */
+class AppCompatResizeOverrides {
+
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+
+ @NonNull
+ private final OptPropFactory.OptProp mAllowForceResizeOverrideOptProp;
+
+ AppCompatResizeOverrides(@NonNull ActivityRecord activityRecord,
+ @NonNull OptPropFactory optPropBuilder) {
+ mActivityRecord = activityRecord;
+ mAllowForceResizeOverrideOptProp = optPropBuilder.create(
+ PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ }
+
+ /**
+ * 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
+ * mode when the window container resizes, such as display size change or screen rotation.
+ *
+ * <p>This method returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>Opt-out component property isn't enabled
+ * <li>Per-app override is enabled
+ * </ul>
+ */
+ boolean shouldOverrideForceResizeApp() {
+ return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
+ isChangeEnabled(mActivityRecord, FORCE_RESIZE_APP));
+ }
+
+ /**
+ * Whether we should apply the force non resize per-app override. When this override is applied
+ * it forces the packages it is applied to to be non-resizable.
+ *
+ * <p>This method returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>Opt-out component property isn't enabled
+ * <li>Per-app override is enabled
+ * </ul>
+ */
+ boolean shouldOverrideForceNonResizeApp() {
+ return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
+ isChangeEnabled(mActivityRecord, FORCE_NON_RESIZE_APP));
+ }
+}
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..9996bbc 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -41,7 +41,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 +106,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/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 75724eb..86f69cd 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -267,7 +267,8 @@
// between fullscreen and PiP would work well. Checking TaskFragment rather than
// Task to ensure that Activity Embedding is excluded.
&& activity.getTaskFragment().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && activity.mLetterboxUiController.isOverrideRespectRequestedOrientationEnabled();
+ && activity.mAppCompatController.getAppCompatOrientationOverrides()
+ .isOverrideRespectRequestedOrientationEnabled();
}
boolean getIgnoreOrientationRequest() {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1efb3ef..403c307 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -550,7 +550,6 @@
/** Save allocating when calculating rects */
private final Rect mTmpRect = new Rect();
- private final Rect mTmpRect2 = new Rect();
private final Region mTmpRegion = new Region();
private final Configuration mTmpConfiguration = new Configuration();
@@ -2940,8 +2939,9 @@
if (!handlesOrientationChangeFromDescendant(orientation)) {
ActivityRecord topActivity = topRunningActivity(/* considerKeyguardState= */ true);
- if (topActivity != null && topActivity.mLetterboxUiController
- .shouldUseDisplayLandscapeNaturalOrientation()) {
+ if (topActivity != null && topActivity.mAppCompatController
+ .getAppCompatOrientationOverrides()
+ .shouldUseDisplayLandscapeNaturalOrientation()) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is ignoring orientation request for %d, return %d"
+ " following a per-app override for %s",
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index ba74f50..59435b8 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -456,10 +456,6 @@
}
}
- InputChannel getInputChannel() {
- return mInputInterceptor == null ? null : mInputInterceptor.mClientChannel;
- }
-
InputWindowHandle getInputWindowHandle() {
return mInputInterceptor == null ? null : mInputInterceptor.mDragWindowHandle;
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index be8e806..444097a 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -116,83 +116,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
- * mode when the window container resizes, such as display size change or screen rotation.
- *
- * <p>This method returns {@code true} when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Per-app override is enabled
- * </ul>
- */
- boolean shouldOverrideForceResizeApp() {
- return getAppCompatOverrides().shouldOverrideForceResizeApp();
- }
-
- /**
- * Whether we should apply the force non resize per-app override. When this override is applied
- * it forces the packages it is applied to to be non-resizable.
- *
- * <p>This method returns {@code true} when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Per-app override is enabled
- * </ul>
- */
- boolean shouldOverrideForceNonResizeApp() {
- return getAppCompatOverrides().shouldOverrideForceNonResizeApp();
- }
-
- /**
- * Sets whether an activity is relaunching after the app has called {@link
- * android.app.Activity#setRequestedOrientation}.
- */
- void setRelaunchingAfterRequestedOrientationChanged(boolean isRelaunching) {
- getAppCompatOverrides().getAppCompatOrientationOverrides()
- .setRelaunchingAfterRequestedOrientationChanged(isRelaunching);
- }
-
-
- boolean isOverrideRespectRequestedOrientationEnabled() {
- return getAppCompatOverrides().isOverrideRespectRequestedOrientationEnabled();
- }
-
- /**
- * Whether should fix display orientation to landscape natural orientation when a task is
- * fullscreen and the display is ignoring orientation requests.
- *
- * <p>This treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Opt-in per-app override is enabled
- * <li>Task is in fullscreen.
- * <li>{@link DisplayContent#getIgnoreOrientationRequest} is enabled
- * <li>Natural orientation of the display is landscape.
- * </ul>
- */
- boolean shouldUseDisplayLandscapeNaturalOrientation() {
- return getAppCompatOverrides().shouldUseDisplayLandscapeNaturalOrientation();
- }
-
boolean hasWallpaperBackgroundForLetterbox() {
return mShowWallpaperForLetterboxBackground;
}
@@ -816,11 +739,6 @@
return null;
}
- boolean getIsRelaunchingAfterRequestedOrientationChanged() {
- return getAppCompatOverrides().getAppCompatOrientationOverrides()
- .getIsRelaunchingAfterRequestedOrientationChanged();
- }
-
private void adjustBoundsForTaskbar(final WindowState mainWindow, final Rect bounds) {
// Rounded corners should be displayed above the taskbar. When taskbar is hidden,
// an insets frame is equal to a navigation bar which shouldn't affect position of
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index a1e6701..2da1fec 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -148,8 +148,7 @@
}
// For snapshot surface, the top activity could be trampoline activity, so here should
// search for top fullscreen activity in the task.
- final WindowState mainWindow = task
- .getTopFullscreenMainWindow(false /* includeStartingApp */);
+ final WindowState mainWindow = task.getTopFullscreenMainWindow();
if (mainWindow == null) {
Slog.w(TAG, "TaskSnapshotSurface.create: no main window in " + activity);
return null;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 259ca83..ab1e969 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1132,6 +1132,9 @@
final Task oldParentTask = oldParent.asTask();
if (oldParentTask != null) {
forAllActivities(oldParentTask::cleanUpActivityReferences);
+
+ // Update the task description of the previous parent as well
+ oldParentTask.updateTaskDescription();
}
if (newParent == null || !newParent.inPinnedWindowingMode()) {
@@ -1163,6 +1166,9 @@
} catch (RemoteException e) {
}
}
+
+ // Update the ancestor tasks' task description after reparenting
+ updateTaskDescription();
}
// First time we are adding the task to the system.
@@ -3007,17 +3013,9 @@
return r.getTask().mTaskId != taskId && r.token != notTop && r.canBeTopRunning();
}
- WindowState getTopFullscreenMainWindow(boolean includeStartingApp) {
- final WindowState[] candidate = new WindowState[1];
- getActivity((r) -> {
- final WindowState win = r.findMainWindow(includeStartingApp);
- if (win != null && win.mAttrs.isFullscreen()) {
- candidate[0] = win;
- return true;
- }
- return false;
- });
- return candidate[0];
+ @Nullable
+ WindowState getTopFullscreenMainWindow() {
+ return getWindow(w -> w.mAttrs.type == TYPE_BASE_APPLICATION && w.mAttrs.isFullscreen());
}
/**
@@ -3201,6 +3199,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
@@ -3353,7 +3359,7 @@
//TODO (AM refactor): Just use local once updateEffectiveIntent is run during all child
// order changes.
- final Task topTask = top != null ? top.getTask() : this;
+ final Task topTask = top != null && top.getTask() != null ? top.getTask() : this;
info.resizeMode = topTask.mResizeMode;
info.topActivityType = topTask.getActivityType();
info.displayCutoutInsets = topTask.getDisplayCutoutInsets();
@@ -3585,8 +3591,7 @@
// starting window because persisted configuration does not effect to Task.
info.taskInfo.configuration.setTo(activity.getConfiguration());
if (!Flags.drawSnapshotAspectRatioMatch()) {
- final WindowState mainWindow =
- getTopFullscreenMainWindow(false /* includeStartingApp */);
+ final WindowState mainWindow = getTopFullscreenMainWindow();
if (mainWindow != null) {
info.topOpaqueWindowInsetsState =
mainWindow.getInsetsStateWithVisibilityOverride();
@@ -6139,9 +6144,8 @@
@Override
void onChildPositionChanged(WindowContainer child) {
- dispatchTaskInfoChangedIfNeeded(false /* force */);
-
if (!mChildren.contains(child)) {
+ dispatchTaskInfoChangedIfNeeded(false /* force */);
return;
}
if (child.asTask() != null) {
@@ -6153,6 +6157,10 @@
// Send for TaskFragmentParentInfo#hasDirectActivity change.
sendTaskFragmentParentInfoChangedIfNeeded();
}
+
+ // Update the ancestor tasks' task description after any children have reparented
+ updateTaskDescription();
+ dispatchTaskInfoChangedIfNeeded(false /* force */);
}
void reparent(TaskDisplayArea newParent, boolean onTop) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 5820a1e..c83b280 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1610,18 +1610,15 @@
if (DEBUG_RESULTS) {
Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a);
}
- final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
- next.token, a);
- mAtmService.getLifecycleManager().scheduleTransactionItem(
- appThread, activityResultItem);
+ final ActivityResultItem item = new ActivityResultItem(next.token, a);
+ mAtmService.getLifecycleManager().scheduleTransactionItem(appThread, item);
}
}
if (next.newIntents != null) {
- final NewIntentItem newIntentItem = NewIntentItem.obtain(
- next.token, next.newIntents, true /* resume */);
- mAtmService.getLifecycleManager().scheduleTransactionItem(
- appThread, newIntentItem);
+ final NewIntentItem item =
+ new NewIntentItem(next.token, next.newIntents, true /* resume */);
+ mAtmService.getLifecycleManager().scheduleTransactionItem(appThread, item);
}
// Well the app will no longer be stopped.
@@ -3105,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..1f82cdb 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;
}
@@ -261,11 +267,6 @@
}
@Override
- WindowState getTopFullscreenWindow(Task source) {
- return source.getTopFullscreenMainWindow(true /* includeStartingApp */);
- }
-
- @Override
ActivityManager.TaskDescription getTaskDescription(Task source) {
return source.getTaskDescription();
}
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 cd785e5..87e120c 100644
--- a/services/core/java/com/android/server/wm/WindowContextListenerController.java
+++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java
@@ -330,8 +330,8 @@
mLastReportedConfig.setTo(config);
mLastReportedDisplay = displayId;
- mWpc.scheduleClientTransactionItem(WindowContextInfoChangeItem.obtain(
- mClientToken, config, displayId));
+ mWpc.scheduleClientTransactionItem(
+ new WindowContextInfoChangeItem(mClientToken, config, displayId));
mHasPendingConfiguration = false;
}
@@ -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/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 8ae1cf0..c6aaf4e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -380,7 +380,7 @@
InputChannel source) {
return state.register(display)
.thenApply(unused ->
- service.startDragAndDrop(source, state.getInputChannel()));
+ service.startDragAndDrop(source.getToken(), state.getInputToken()));
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 984caf1..2bae0a8 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -437,7 +437,7 @@
final ConfigurationChangeItem configurationChangeItem;
synchronized (mLastReportedConfiguration) {
onConfigurationChangePreScheduled(mLastReportedConfiguration);
- configurationChangeItem = ConfigurationChangeItem.obtain(
+ configurationChangeItem = new ConfigurationChangeItem(
mLastReportedConfiguration, mLastTopActivityDeviceId);
}
// Schedule immediately to make sure the app component (e.g. receiver, service) can get
@@ -1721,8 +1721,8 @@
}
onConfigurationChangePreScheduled(config);
- scheduleClientTransactionItem(thread, ConfigurationChangeItem.obtain(
- config, mLastTopActivityDeviceId));
+ scheduleClientTransactionItem(
+ thread, new ConfigurationChangeItem(config, mLastTopActivityDeviceId));
}
private void onConfigurationChangePreScheduled(@NonNull Configuration config) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 240978a..1cc5a8b 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 */);
}
}
@@ -5350,7 +5373,7 @@
// change then delay the position update until it has redrawn to avoid any flickers.
final boolean isLetterboxedAndRelaunching = activityRecord != null
&& activityRecord.areBoundsLetterboxed()
- && activityRecord.mLetterboxUiController
+ && activityRecord.mAppCompatController.getAppCompatOrientationOverrides()
.getIsRelaunchingAfterRequestedOrientationChanged();
if (surfaceResizedWithoutMoveAnimation || isLetterboxedAndRelaunching) {
applyWithNextDraw(mSetSurfacePositionConsumer);
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/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index e6dac88..dab3978 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -354,6 +354,9 @@
private static void createAndUploadReport(ProfcollectForwardingService pfs) {
BackgroundThread.get().getThreadHandler().post(() -> {
+ if (pfs.mIProfcollect == null) {
+ return;
+ }
String reportName;
try {
reportName = pfs.mIProfcollect.report(pfs.mUsageSetting) + ".zip";
@@ -401,6 +404,9 @@
final int traceDuration = 5000;
final String traceTag = "camera";
BackgroundThread.get().getThreadHandler().post(() -> {
+ if (mIProfcollect == null) {
+ return;
+ }
try {
mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider",
traceDuration);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index 337d5c1..faa198c 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -49,6 +49,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.wm.ImeTargetChangeListener;
import com.android.server.wm.WindowManagerInternal;
@@ -93,33 +94,37 @@
@Test
public void testRequestImeVisibility_showImplicit() {
- initImeTargetWindowState(mWindowToken);
- boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
- InputMethodManager.SHOW_IMPLICIT);
- mComputer.requestImeVisibility(mWindowToken, res);
+ synchronized (ImfLock.class) {
+ initImeTargetWindowState(mWindowToken);
+ boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
+ InputMethodManager.SHOW_IMPLICIT);
+ mComputer.requestImeVisibility(mWindowToken, res);
- final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
- assertThat(state).isNotNull();
- assertThat(state.hasEditorFocused()).isTrue();
- assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
- assertThat(state.isRequestedImeVisible()).isTrue();
+ final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+ assertThat(state).isNotNull();
+ assertThat(state.hasEditorFocused()).isTrue();
+ assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+ assertThat(state.isRequestedImeVisible()).isTrue();
- assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+ assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+ }
}
@Test
public void testRequestImeVisibility_showExplicit() {
- initImeTargetWindowState(mWindowToken);
- boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
- mComputer.requestImeVisibility(mWindowToken, res);
+ synchronized (ImfLock.class) {
+ initImeTargetWindowState(mWindowToken);
+ boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
+ mComputer.requestImeVisibility(mWindowToken, res);
- final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
- assertThat(state).isNotNull();
- assertThat(state.hasEditorFocused()).isTrue();
- assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
- assertThat(state.isRequestedImeVisible()).isTrue();
+ final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+ assertThat(state).isNotNull();
+ assertThat(state.hasEditorFocused()).isTrue();
+ assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+ assertThat(state.isRequestedImeVisible()).isTrue();
- assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+ assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+ }
}
/**
@@ -128,12 +133,14 @@
*/
@Test
public void testRequestImeVisibility_showExplicit_thenShowImplicit() {
- initImeTargetWindowState(mWindowToken);
- mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
- assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+ synchronized (ImfLock.class) {
+ initImeTargetWindowState(mWindowToken);
+ mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
+ assertThat(mComputer.mRequestedShowExplicitly).isTrue();
- mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT);
- assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+ mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT);
+ assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+ }
}
/**
@@ -142,162 +149,183 @@
*/
@Test
public void testRequestImeVisibility_showForced_thenShowExplicit() {
- initImeTargetWindowState(mWindowToken);
- mComputer.onImeShowFlags(ImeTracker.Token.empty(), InputMethodManager.SHOW_FORCED);
- assertThat(mComputer.mShowForced).isTrue();
+ synchronized (ImfLock.class) {
+ initImeTargetWindowState(mWindowToken);
+ mComputer.onImeShowFlags(ImeTracker.Token.empty(), InputMethodManager.SHOW_FORCED);
+ assertThat(mComputer.mShowForced).isTrue();
- mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
- assertThat(mComputer.mShowForced).isTrue();
+ mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
+ assertThat(mComputer.mShowForced).isTrue();
+ }
}
@Test
public void testRequestImeVisibility_showImplicit_a11yNoImePolicy() {
- // Precondition: set AccessibilityService#SHOW_MODE_HIDDEN policy
- mComputer.getImePolicy().setA11yRequestNoSoftKeyboard(SHOW_MODE_HIDDEN);
+ synchronized (ImfLock.class) {
+ // Precondition: set AccessibilityService#SHOW_MODE_HIDDEN policy
+ mComputer.getImePolicy().setA11yRequestNoSoftKeyboard(SHOW_MODE_HIDDEN);
- initImeTargetWindowState(mWindowToken);
- boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
- InputMethodManager.SHOW_IMPLICIT);
- mComputer.requestImeVisibility(mWindowToken, res);
+ initImeTargetWindowState(mWindowToken);
+ boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
+ InputMethodManager.SHOW_IMPLICIT);
+ mComputer.requestImeVisibility(mWindowToken, res);
- final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
- assertThat(state).isNotNull();
- assertThat(state.hasEditorFocused()).isTrue();
- assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
- assertThat(state.isRequestedImeVisible()).isFalse();
+ final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+ assertThat(state).isNotNull();
+ assertThat(state.hasEditorFocused()).isTrue();
+ assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+ assertThat(state.isRequestedImeVisible()).isFalse();
- assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+ assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+ }
}
@Test
public void testRequestImeVisibility_showImplicit_imeHiddenPolicy() {
- // Precondition: set IME hidden display policy before calling showSoftInput
- mComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
+ synchronized (ImfLock.class) {
+ // Precondition: set IME hidden display policy before calling showSoftInput
+ mComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
- initImeTargetWindowState(mWindowToken);
- boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
- InputMethodManager.SHOW_IMPLICIT);
- mComputer.requestImeVisibility(mWindowToken, res);
+ initImeTargetWindowState(mWindowToken);
+ boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
+ InputMethodManager.SHOW_IMPLICIT);
+ mComputer.requestImeVisibility(mWindowToken, res);
- final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
- assertThat(state).isNotNull();
- assertThat(state.hasEditorFocused()).isTrue();
- assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
- assertThat(state.isRequestedImeVisible()).isFalse();
+ final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+ assertThat(state).isNotNull();
+ assertThat(state.hasEditorFocused()).isTrue();
+ assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+ assertThat(state.isRequestedImeVisible()).isFalse();
- assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+ assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+ }
}
@Test
public void testRequestImeVisibility_hideNotAlways() {
- // Precondition: ensure IME has shown before hiding request.
- mComputer.setInputShown(true);
+ synchronized (ImfLock.class) {
+ // Precondition: ensure IME has shown before hiding request.
+ mComputer.setInputShown(true);
- initImeTargetWindowState(mWindowToken);
- assertThat(mComputer.canHideIme(ImeTracker.Token.empty(),
- InputMethodManager.HIDE_NOT_ALWAYS)).isTrue();
- mComputer.requestImeVisibility(mWindowToken, false);
+ initImeTargetWindowState(mWindowToken);
+ assertThat(mComputer.canHideIme(ImeTracker.Token.empty(),
+ InputMethodManager.HIDE_NOT_ALWAYS)).isTrue();
+ mComputer.requestImeVisibility(mWindowToken, false);
- final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
- assertThat(state).isNotNull();
- assertThat(state.hasEditorFocused()).isTrue();
- assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
- assertThat(state.isRequestedImeVisible()).isFalse();
+ final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+ assertThat(state).isNotNull();
+ assertThat(state.hasEditorFocused()).isTrue();
+ assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+ assertThat(state.isRequestedImeVisible()).isFalse();
+ }
}
@Test
public void testComputeImeDisplayId() {
- final ImeTargetWindowState state = mComputer.getOrCreateWindowState(mWindowToken);
+ synchronized (ImfLock.class) {
+ final ImeTargetWindowState state = mComputer.getOrCreateWindowState(mWindowToken);
- mImeDisplayPolicy = DISPLAY_IME_POLICY_LOCAL;
- mComputer.computeImeDisplayId(state, DEFAULT_DISPLAY);
- assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
- assertThat(state.getImeDisplayId()).isEqualTo(DEFAULT_DISPLAY);
+ mImeDisplayPolicy = DISPLAY_IME_POLICY_LOCAL;
+ mComputer.computeImeDisplayId(state, DEFAULT_DISPLAY);
+ assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
+ assertThat(state.getImeDisplayId()).isEqualTo(DEFAULT_DISPLAY);
- mComputer.computeImeDisplayId(state, 10 /* displayId */);
- assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
- assertThat(state.getImeDisplayId()).isEqualTo(10);
+ mComputer.computeImeDisplayId(state, 10 /* displayId */);
+ assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
+ assertThat(state.getImeDisplayId()).isEqualTo(10);
- mImeDisplayPolicy = DISPLAY_IME_POLICY_HIDE;
- mComputer.computeImeDisplayId(state, 10 /* displayId */);
- assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isTrue();
- assertThat(state.getImeDisplayId()).isEqualTo(INVALID_DISPLAY);
+ mImeDisplayPolicy = DISPLAY_IME_POLICY_HIDE;
+ mComputer.computeImeDisplayId(state, 10 /* displayId */);
+ assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isTrue();
+ assertThat(state.getImeDisplayId()).isEqualTo(INVALID_DISPLAY);
- mImeDisplayPolicy = DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
- mComputer.computeImeDisplayId(state, 10 /* displayId */);
- assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
- assertThat(state.getImeDisplayId()).isEqualTo(FALLBACK_DISPLAY_ID);
+ mImeDisplayPolicy = DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+ mComputer.computeImeDisplayId(state, 10 /* displayId */);
+ assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
+ assertThat(state.getImeDisplayId()).isEqualTo(FALLBACK_DISPLAY_ID);
+ }
}
@Test
public void testComputeState_lastImeRequestedVisible_preserved_When_StateUnChanged() {
- // Assume the last IME targeted window has requested IME visible
- final IBinder lastImeTargetWindowToken = new Binder();
- mInputMethodManagerService.mLastImeTargetWindow = lastImeTargetWindowToken;
- mComputer.requestImeVisibility(lastImeTargetWindowToken, true);
- final ImeTargetWindowState lastState = mComputer.getWindowStateOrNull(
- lastImeTargetWindowToken);
- assertThat(lastState.isRequestedImeVisible()).isTrue();
+ synchronized (ImfLock.class) {
+ // Assume the last IME targeted window has requested IME visible
+ final IBinder lastImeTargetWindowToken = new Binder();
+ mInputMethodManagerService.mLastImeTargetWindow = lastImeTargetWindowToken;
+ mComputer.requestImeVisibility(lastImeTargetWindowToken, true);
+ final ImeTargetWindowState lastState = mComputer.getWindowStateOrNull(
+ lastImeTargetWindowToken);
+ assertThat(lastState.isRequestedImeVisible()).isTrue();
- // Verify when focusing the next window with STATE_UNCHANGED flag, the last IME
- // visibility state will be preserved to the current window state.
- final ImeTargetWindowState stateWithUnChangedFlag = initImeTargetWindowState(mWindowToken);
- mComputer.computeState(stateWithUnChangedFlag, true /* allowVisible */);
- assertThat(stateWithUnChangedFlag.isRequestedImeVisible()).isEqualTo(
- lastState.isRequestedImeVisible());
+ // Verify when focusing the next window with STATE_UNCHANGED flag, the last IME
+ // visibility state will be preserved to the current window state.
+ final ImeTargetWindowState stateWithUnChangedFlag = initImeTargetWindowState(
+ mWindowToken);
+ mComputer.computeState(stateWithUnChangedFlag, true /* allowVisible */);
+ assertThat(stateWithUnChangedFlag.isRequestedImeVisible()).isEqualTo(
+ lastState.isRequestedImeVisible());
+ }
}
@Test
public void testOnInteractiveChanged() {
- mComputer.getOrCreateWindowState(mWindowToken);
- // Precondition: ensure IME has shown before hiding request.
- mComputer.requestImeVisibility(mWindowToken, true);
- mComputer.setInputShown(true);
+ synchronized (ImfLock.class) {
+ mComputer.getOrCreateWindowState(mWindowToken);
+ // Precondition: ensure IME has shown before hiding request.
+ mComputer.requestImeVisibility(mWindowToken, true);
+ mComputer.setInputShown(true);
- // No need any visibility change When initially shows IME on the device was interactive.
- ImeVisibilityStateComputer.ImeVisibilityResult result = mComputer.onInteractiveChanged(
- mWindowToken, true /* interactive */);
- assertThat(result).isNull();
+ // No need any visibility change When initially shows IME on the device was interactive.
+ ImeVisibilityStateComputer.ImeVisibilityResult result = mComputer.onInteractiveChanged(
+ mWindowToken, true /* interactive */);
+ assertThat(result).isNull();
- // Show the IME screenshot to capture the last IME visible state when the device inactive.
- result = mComputer.onInteractiveChanged(mWindowToken, false /* interactive */);
- assertThat(result).isNotNull();
- assertThat(result.getState()).isEqualTo(STATE_SHOW_IME_SNAPSHOT);
- assertThat(result.getReason()).isEqualTo(SHOW_IME_SCREENSHOT_FROM_IMMS);
+ // Show the IME screenshot to capture the last IME visible state when the device
+ // inactive.
+ result = mComputer.onInteractiveChanged(mWindowToken, false /* interactive */);
+ assertThat(result).isNotNull();
+ assertThat(result.getState()).isEqualTo(STATE_SHOW_IME_SNAPSHOT);
+ assertThat(result.getReason()).isEqualTo(SHOW_IME_SCREENSHOT_FROM_IMMS);
- // Remove the IME screenshot when the device became interactive again.
- result = mComputer.onInteractiveChanged(mWindowToken, true /* interactive */);
- assertThat(result).isNotNull();
- assertThat(result.getState()).isEqualTo(STATE_REMOVE_IME_SNAPSHOT);
- assertThat(result.getReason()).isEqualTo(REMOVE_IME_SCREENSHOT_FROM_IMMS);
+ // Remove the IME screenshot when the device became interactive again.
+ result = mComputer.onInteractiveChanged(mWindowToken, true /* interactive */);
+ assertThat(result).isNotNull();
+ assertThat(result.getState()).isEqualTo(STATE_REMOVE_IME_SNAPSHOT);
+ assertThat(result.getReason()).isEqualTo(REMOVE_IME_SCREENSHOT_FROM_IMMS);
+ }
}
@Test
public void testOnApplyImeVisibilityFromComputer() {
- final IBinder testImeTargetOverlay = new Binder();
- final IBinder testImeInputTarget = new Binder();
+ synchronized (ImfLock.class) {
+ final IBinder testImeTargetOverlay = new Binder();
+ final IBinder testImeInputTarget = new Binder();
- // Simulate a test IME input target was visible.
- mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, true, false);
+ // Simulate a test IME input target was visible.
+ mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, true, false);
- // Simulate a test IME layering target overlay fully occluded the IME input target.
- mListener.onImeTargetOverlayVisibilityChanged(testImeTargetOverlay,
- TYPE_APPLICATION_OVERLAY, true, false);
- mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, false, false);
- final ArgumentCaptor<IBinder> targetCaptor = ArgumentCaptor.forClass(IBinder.class);
- final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass(
- ImeVisibilityResult.class);
- verify(mInputMethodManagerService).onApplyImeVisibilityFromComputer(targetCaptor.capture(),
- notNull() /* statsToken */, resultCaptor.capture());
- final IBinder imeInputTarget = targetCaptor.getValue();
- final ImeVisibilityResult result = resultCaptor.getValue();
+ // Simulate a test IME layering target overlay fully occluded the IME input target.
+ mListener.onImeTargetOverlayVisibilityChanged(testImeTargetOverlay,
+ TYPE_APPLICATION_OVERLAY, true, false);
+ mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, false, false);
+ final ArgumentCaptor<IBinder> targetCaptor = ArgumentCaptor.forClass(IBinder.class);
+ final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass(
+ ImeVisibilityResult.class);
+ synchronized (ImfLock.class) {
+ verify(mInputMethodManagerService).onApplyImeVisibilityFromComputerLocked(
+ targetCaptor.capture(), notNull() /* statsToken */, resultCaptor.capture());
+ }
+ final IBinder imeInputTarget = targetCaptor.getValue();
+ final ImeVisibilityResult result = resultCaptor.getValue();
- // Verify the computer will callback hiding IME state to IMMS.
- assertThat(imeInputTarget).isEqualTo(testImeInputTarget);
- assertThat(result.getState()).isEqualTo(STATE_HIDE_IME_EXPLICIT);
- assertThat(result.getReason()).isEqualTo(HIDE_WHEN_INPUT_TARGET_INVISIBLE);
+ // Verify the computer will callback hiding IME state to IMMS.
+ assertThat(imeInputTarget).isEqualTo(testImeInputTarget);
+ assertThat(result.getState()).isEqualTo(STATE_HIDE_IME_EXPLICIT);
+ assertThat(result.getReason()).isEqualTo(HIDE_WHEN_INPUT_TARGET_INVISIBLE);
+ }
}
+ @GuardedBy("ImfLock.class")
private ImeTargetWindowState initImeTargetWindowState(IBinder windowToken) {
final ImeTargetWindowState state = new ImeTargetWindowState(SOFT_INPUT_STATE_UNCHANGED,
0, true, true, true);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImmutableSparseArrayTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImmutableSparseArrayTest.java
new file mode 100644
index 0000000..944b7c6
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImmutableSparseArrayTest.java
@@ -0,0 +1,278 @@
+/*
+ * 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public final class ImmutableSparseArrayTest {
+
+ @Test
+ public void testEmptyObject() {
+ final ImmutableSparseArray<Object> empty = ImmutableSparseArray.empty();
+
+ assertThat(empty.size()).isEqualTo(0);
+ verifyCommonBehaviors(empty);
+ }
+
+ @Test
+ public void testEmptyMethod() {
+ assertThat(ImmutableSparseArray.empty()).isSameInstanceAs(ImmutableSparseArray.empty());
+ }
+
+ @Test
+ public void testCloneWithPutOrSelf_appendingFromEmpty() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = -2; // intentionally negative
+ final Object value2 = new Object();
+ final int key3 = -3; // intentionally negative
+ final Object value3 = new Object();
+ final int key4 = 4;
+ final Object value4 = new Object();
+
+ final ImmutableSparseArray<Object> oneItemArray = ImmutableSparseArray.empty()
+ .cloneWithPutOrSelf(key1, value1);
+ verifyCommonBehaviors(oneItemArray);
+ assertThat(oneItemArray.size()).isEqualTo(1);
+ assertThat(oneItemArray.get(key1)).isSameInstanceAs(value1);
+
+ final ImmutableSparseArray<Object> twoItemArray =
+ oneItemArray.cloneWithPutOrSelf(key2, value2);
+ assertThat(twoItemArray).isNotSameInstanceAs(oneItemArray);
+ verifyCommonBehaviors(twoItemArray);
+ assertThat(twoItemArray.size()).isEqualTo(2);
+ assertThat(twoItemArray.get(key1)).isSameInstanceAs(value1);
+ assertThat(twoItemArray.get(key2)).isSameInstanceAs(value2);
+
+ final ImmutableSparseArray<Object> threeItemArray =
+ twoItemArray.cloneWithPutOrSelf(key3, value3);
+ assertThat(threeItemArray).isNotSameInstanceAs(twoItemArray);
+ verifyCommonBehaviors(threeItemArray);
+ assertThat(threeItemArray.size()).isEqualTo(3);
+ assertThat(threeItemArray.get(key1)).isSameInstanceAs(value1);
+ assertThat(threeItemArray.get(key2)).isSameInstanceAs(value2);
+ assertThat(threeItemArray.get(key3)).isSameInstanceAs(value3);
+
+ final ImmutableSparseArray<Object> fourItemArray =
+ threeItemArray.cloneWithPutOrSelf(key4, value4);
+ assertThat(fourItemArray).isNotSameInstanceAs(threeItemArray);
+ verifyCommonBehaviors(fourItemArray);
+ assertThat(fourItemArray.size()).isEqualTo(4);
+ assertThat(fourItemArray.get(key1)).isSameInstanceAs(value1);
+ assertThat(fourItemArray.get(key2)).isSameInstanceAs(value2);
+ assertThat(fourItemArray.get(key3)).isSameInstanceAs(value3);
+ assertThat(fourItemArray.get(key4)).isSameInstanceAs(value4);
+ }
+
+ @Test
+ public void testCloneWithPutOrSelf_returnSelf() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1);
+ assertThat(array.cloneWithPutOrSelf(key1, value1)).isSameInstanceAs(array);
+ }
+
+ @Test
+ public void testCloneWithPutOrSelf_updateExistingValue() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final Object value2updated = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3);
+
+ final var updatedArray = array.cloneWithPutOrSelf(key2, value2updated);
+ verifyCommonBehaviors(updatedArray);
+
+ assertThat(updatedArray.size()).isEqualTo(3);
+ assertThat(updatedArray.get(key1)).isSameInstanceAs(value1);
+ assertThat(updatedArray.get(key2)).isSameInstanceAs(value2updated);
+ assertThat(updatedArray.get(key3)).isSameInstanceAs(value3);
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_empty() {
+ final ImmutableSparseArray<Object> empty = ImmutableSparseArray.empty();
+ assertThat(empty.cloneWithRemoveOrSelf(0)).isSameInstanceAs(empty);
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_singleInstance() {
+ final int key = 1;
+ final Object value = new Object();
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key, value);
+ assertThat(array.cloneWithRemoveOrSelf(key)).isSameInstanceAs(ImmutableSparseArray.empty());
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_firstItem() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3)
+ .cloneWithRemoveOrSelf(key1);
+ verifyCommonBehaviors(array);
+
+ assertThat(array.size()).isEqualTo(2);
+ assertThat(array.get(key1)).isNull();
+ assertThat(array.get(key2)).isSameInstanceAs(value2);
+ assertThat(array.get(key3)).isSameInstanceAs(value3);
+ assertThat(array.keyAt(0)).isEqualTo(key2);
+ assertThat(array.keyAt(1)).isEqualTo(key3);
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_lastItem() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3)
+ .cloneWithRemoveOrSelf(key3);
+ verifyCommonBehaviors(array);
+
+ assertThat(array.size()).isEqualTo(2);
+ assertThat(array.get(key1)).isSameInstanceAs(value1);
+ assertThat(array.get(key2)).isSameInstanceAs(value2);
+ assertThat(array.get(key3)).isNull();
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_middleItem() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3)
+ .cloneWithRemoveOrSelf(key2);
+ verifyCommonBehaviors(array);
+
+ assertThat(array.size()).isEqualTo(2);
+ assertThat(array.get(key1)).isSameInstanceAs(value1);
+ assertThat(array.get(key2)).isNull();
+ assertThat(array.get(key3)).isSameInstanceAs(value3);
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_nonExistentItem() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+ final int key4 = 4;
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3);
+
+ assertThat(array.cloneWithRemoveOrSelf(key4)).isSameInstanceAs(array);
+ }
+
+ @Test
+ public void testForEach() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3);
+
+ final ArrayList<Object> list = new ArrayList<>();
+ array.forEach(list::add);
+ assertThat(list).containsExactlyElementsIn(new Object[]{ value1, value2, value3 })
+ .inOrder();
+ }
+
+
+ private void verifyCommonBehaviors(@NonNull ImmutableSparseArray<Object> sparseArray) {
+ verifyInvalidKeyBehaviors(sparseArray);
+ verifyOutOfBoundsBehaviors(sparseArray);
+ }
+
+ private void verifyInvalidKeyBehaviors(@NonNull ImmutableSparseArray<Object> sparseArray) {
+ final int invalid_key = -123456678;
+ assertThat(sparseArray.get(invalid_key)).isNull();
+ assertThat(sparseArray.indexOfKey(invalid_key)).isEqualTo(-1);
+ }
+
+ private void verifyOutOfBoundsBehaviors(@NonNull ImmutableSparseArray<Object> sparseArray) {
+ final int size = sparseArray.size();
+ assertThrows(ArrayIndexOutOfBoundsException.class,
+ () -> sparseArray.keyAt(size));
+ assertThrows(ArrayIndexOutOfBoundsException.class,
+ () -> sparseArray.valueAt(size));
+ assertThrows(ArrayIndexOutOfBoundsException.class,
+ () -> sparseArray.keyAt(-1));
+ assertThrows(ArrayIndexOutOfBoundsException.class,
+ () -> sparseArray.valueAt(-1));
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index ec9bfa7..d427a6d 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -272,7 +272,7 @@
// Certain tests rely on TEST_IME_ID that is installed with AndroidTest.xml.
// TODO(b/352615651): Consider just synthesizing test InputMethodInfo then injecting it.
- AdditionalSubtypeMapRepository.ensureInitializedAndGet(mCallingUserId);
+ AdditionalSubtypeMapRepository.initializeIfNecessary(mCallingUserId);
final var settings = InputMethodManagerService.queryInputMethodServicesInternal(mContext,
mCallingUserId, AdditionalSubtypeMapRepository.get(mCallingUserId),
DirectBootAwareness.AUTO);
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/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
index 98ba9ae..abb354b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
@@ -151,9 +151,10 @@
}
@Test
- public void testDisplayInteractivityChanges(
+ public void testDisplayInteractivityChangesWhileMirroring(
@TestParameter final boolean isExternalDisplayUsedForAudio) {
mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
+ mExternalDisplayStatsService.onDisplayAdded(EXTERNAL_DISPLAY_ID);
mHandler.flush();
assertThat(mInteractivityReceiver).isNotNull();
@@ -180,12 +181,38 @@
mInteractivityReceiver.onReceive(null, null);
assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
verify(mMockedInjector).writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
- FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__CONNECTED,
+ FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__MIRRORING,
/*numberOfDisplays=*/ 1,
isExternalDisplayUsedForAudio);
}
@Test
+ public void testDisplayInteractivityChangesWhileConnected() {
+ mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
+ mHandler.flush();
+ assertThat(mInteractivityReceiver).isNotNull();
+ clearInvocations(mMockedInjector);
+
+ // Default is 'interactive', so no log should be written.
+ mInteractivityReceiver.onReceive(null, null);
+ assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
+ verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+
+ // Change to non-interactive should not produce log
+ when(mMockedInjector.isInteractive(eq(EXTERNAL_DISPLAY_ID))).thenReturn(false);
+ mInteractivityReceiver.onReceive(null, null);
+ assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isFalse();
+ verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+ clearInvocations(mMockedInjector);
+
+ // Change back to interactive should not produce log
+ when(mMockedInjector.isInteractive(eq(EXTERNAL_DISPLAY_ID))).thenReturn(true);
+ mInteractivityReceiver.onReceive(null, null);
+ assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
+ verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+ }
+
+ @Test
public void testAudioPlaybackChanges() {
mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
mHandler.flush();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
index 26f6e91..df09b04 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -46,6 +46,7 @@
mBrightnessEvent.setPhysicalDisplayId("987654321");
mBrightnessEvent.setPhysicalDisplayName("display_name");
mBrightnessEvent.setDisplayState(Display.STATE_ON);
+ mBrightnessEvent.setDisplayStateReason(Display.STATE_REASON_DEFAULT_POLICY);
mBrightnessEvent.setDisplayPolicy(POLICY_BRIGHT);
mBrightnessEvent.setLux(100.0f);
mBrightnessEvent.setPercent(46.5f);
@@ -82,9 +83,9 @@
String actualString = mBrightnessEvent.toString(false);
String expectedString =
"BrightnessEvent: brt=0.6 (46.5%), nits= 893.8, lux=100.0, reason=doze [ "
- + "low_pwr ], strat=strategy_name, state=ON, policy=BRIGHT, flags=, "
- + "initBrt=25.0, rcmdBrt=0.6, preBrt=NaN, preLux=150.0, "
- + "wasShortTermModelActive=true, autoBrightness=true (idle), "
+ + "low_pwr ], strat=strategy_name, state=ON, stateReason=DEFAULT_POLICY, "
+ + "policy=BRIGHT, flags=, initBrt=25.0, rcmdBrt=0.6, preBrt=NaN, "
+ + "preLux=150.0, wasShortTermModelActive=true, autoBrightness=true (idle), "
+ "unclampedBrt=0.65, hbmMax=0.62, hbmMode=off, thrmMax=0.65, "
+ "rbcStrength=-1, powerFactor=0.2, physDisp=display_name(987654321), "
+ "logicalId=1";
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index adcbf5c..194bf4b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -442,20 +442,24 @@
IMPORTANCE_FOREGROUND_SERVICE, // importance
null); // description
- // Case 4: Create a process from another package with kill from lmkd
+ /*
+ * Case 4: Create a process from another package with kill from lmkd
+ * We expect LMKD's reported RSS to be the process' last seen RSS.
+ */
final int app2UidUser2 = 1010234;
final int app2PidUser2 = 12348;
final long app2Pss1 = 54321;
final long app2Rss1 = 65432;
+ final long lmkd_reported_rss = 43215;
final String app2ProcessName = "com.android.test.stub2:process";
final String app2PackageName = "com.android.test.stub2";
sleep(1);
final long now4 = System.currentTimeMillis();
- doReturn(new Pair<Long, Object>(now4, Integer.valueOf(0)))
+ doReturn(null)
.when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
.remove(anyInt(), anyInt());
- doReturn(new Pair<Long, Object>(now4, null))
+ doReturn(new Pair<Long, Object>(now4, Long.valueOf(lmkd_reported_rss)))
.when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
.remove(anyInt(), anyInt());
@@ -490,7 +494,7 @@
null, // subReason
0, // status
app2Pss1, // pss
- app2Rss1, // rss
+ lmkd_reported_rss, // rss
IMPORTANCE_CACHED, // importance
null); // description
@@ -499,6 +503,11 @@
mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, 0, list);
assertEquals(1, list.size());
+ info = list.get(0);
+
+ // Verify the AppExitInfo has the LMKD reported RSS
+ assertEquals(lmkd_reported_rss, info.getRss());
+
// Case 5: App native crash
final int app3UidUser2 = 1010345;
final int app3PidUser2 = 12349;
@@ -599,7 +608,7 @@
null, // subReason
0, // status
app2Pss1, // pss
- app2Rss1, // rss
+ lmkd_reported_rss, // rss
IMPORTANCE_CACHED, // importance
null); // description
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerStatsProcessorTest.java
new file mode 100644
index 0000000..8d2849b
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerStatsProcessorTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.power.stats;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
+import android.os.Handler;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.ScreenPowerStatsCollector.Injector;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.IntSupplier;
+
+public class AmbientDisplayPowerStatsProcessorTest {
+
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
+ @Rule(order = 1)
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setNumDisplays(2)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_AMBIENT, 0, 180.0)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_AMBIENT, 1, 360.0);
+
+ private static final double PRECISION = 0.1;
+ private static final int VOLTAGE_MV = 3500;
+
+ @Mock
+ private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ @Mock
+ private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever;
+
+ private final Injector mInjector = new Injector() {
+ @Override
+ public Handler getHandler() {
+ return mStatsRule.getHandler();
+ }
+
+ @Override
+ public Clock getClock() {
+ return mStatsRule.getMockClock();
+ }
+
+ @Override
+ public PowerStatsUidResolver getUidResolver() {
+ return new PowerStatsUidResolver();
+ }
+
+ @Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
+ public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+ return mConsumedEnergyRetriever;
+ }
+
+ @Override
+ public IntSupplier getVoltageSupplier() {
+ return () -> VOLTAGE_MV;
+ }
+
+ @Override
+ public int getDisplayCount() {
+ return 2;
+ }
+
+ @Override
+ public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() {
+ return mScreenUsageTimeRetriever;
+ }
+ };
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void processPowerStats() {
+ PowerComponentAggregatedPowerStats stats = collectAndAggregatePowerStats();
+
+ assertPowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 16.2);
+ assertPowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 5.4);
+ assertPowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_ON, 0);
+ assertPowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_ON, 0);
+ }
+
+ private PowerComponentAggregatedPowerStats collectAndAggregatePowerStats() {
+ ScreenPowerStatsProcessor screenPowerStatsProcessor =
+ new ScreenPowerStatsProcessor(mStatsRule.getPowerProfile());
+ AmbientDisplayPowerStatsProcessor ambientDisplayPowerStatsProcessor =
+ new AmbientDisplayPowerStatsProcessor();
+
+ AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SCREEN)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_SCREEN)
+ .setProcessor(screenPowerStatsProcessor);
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
+ BatteryConsumer.POWER_COMPONENT_SCREEN)
+ .setProcessor(ambientDisplayPowerStatsProcessor);
+
+ AggregatedPowerStats stats = new AggregatedPowerStats(config);
+
+ stats.setDeviceState(STATE_POWER, POWER_STATE_OTHER, 0);
+ stats.setDeviceState(STATE_SCREEN, SCREEN_STATE_OTHER, 0);
+
+ ScreenPowerStatsCollector collector = new ScreenPowerStatsCollector(mInjector);
+ collector.setEnabled(true);
+
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY))
+ .thenReturn(new int[0]);
+
+ stats.addPowerStats(collector.collectStats(), 1000);
+
+ when(mScreenUsageTimeRetriever.getScreenOnTimeMs(0))
+ .thenReturn(60_000L);
+ when(mScreenUsageTimeRetriever.getScreenOnTimeMs(1))
+ .thenReturn(120_000L);
+ when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(0))
+ .thenReturn(180_000L);
+ when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(1))
+ .thenReturn(240_000L);
+ stats.setDeviceState(STATE_POWER, POWER_STATE_BATTERY, 101_000);
+ stats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 401_000);
+
+ // Slightly larger than 600_000 total screen time, to simulate a sight race
+ // between state changes and power stats collection
+ stats.addPowerStats(collector.collectStats(), 612_000);
+
+ stats.finish(612_000);
+
+ return stats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY);
+ }
+
+ private void assertPowerEstimate(PowerComponentAggregatedPowerStats aggregatedStats,
+ int powerState, int screenState, double expectedPowerEstimate) {
+ PowerStats.Descriptor descriptor = aggregatedStats.getPowerStatsDescriptor();
+ PowerStatsLayout layout = new PowerStatsLayout(descriptor);
+ long[] stats = new long[descriptor.statsArrayLength];
+ aggregatedStats.getDeviceStats(stats, new int[]{powerState, screenState});
+ assertThat(layout.getDevicePowerEstimate(stats)).isWithin(PRECISION)
+ .of(expectedPowerEstimate);
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java
new file mode 100644
index 0000000..817fdcb
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.ScreenPowerStatsCollector.Injector;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.IntSupplier;
+
+public class ScreenPowerStatsCollectorTest {
+ private static final int APP_UID1 = 42;
+ private static final int APP_UID2 = 24;
+ private static final int ISOLATED_UID = 99123;
+
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
+ @Rule(order = 1)
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_SCREEN, 1000);
+
+ @Mock
+ private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ @Mock
+ private PowerStatsUidResolver mPowerStatsUidResolver;
+ @Mock
+ private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever;
+
+ private final Injector mInjector = new Injector() {
+ @Override
+ public Handler getHandler() {
+ return mStatsRule.getHandler();
+ }
+
+ @Override
+ public Clock getClock() {
+ return mStatsRule.getMockClock();
+ }
+
+ @Override
+ public PowerStatsUidResolver getUidResolver() {
+ return mPowerStatsUidResolver;
+ }
+
+ @Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
+ public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+ return mConsumedEnergyRetriever;
+ }
+
+ @Override
+ public IntSupplier getVoltageSupplier() {
+ return () -> 3500;
+ }
+
+ @Override
+ public int getDisplayCount() {
+ return 2;
+ }
+
+ @Override
+ public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() {
+ return mScreenUsageTimeRetriever;
+ }
+ };
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mPowerStatsUidResolver.mapUid(anyInt())).thenAnswer(invocation -> {
+ int uid = invocation.getArgument(0);
+ if (uid == ISOLATED_UID) {
+ return APP_UID2;
+ } else {
+ return uid;
+ }
+ });
+ }
+
+ @Test
+ public void collectStats() {
+ ScreenPowerStatsCollector collector = new ScreenPowerStatsCollector(mInjector);
+ collector.setEnabled(true);
+
+ // Establish a baseline
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY))
+ .thenReturn(new int[]{77});
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77}))
+ .thenReturn(new long[]{10_000});
+
+ doAnswer(inv -> {
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback =
+ inv.getArgument(0);
+ callback.onUidTopActivityTime(APP_UID1, 1000);
+ callback.onUidTopActivityTime(APP_UID2, 2000);
+ return null;
+ }).when(mScreenUsageTimeRetriever).retrieveTopActivityTimes(any(
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback.class));
+
+ collector.collectStats();
+
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77}))
+ .thenReturn(new long[]{45_000});
+ when(mScreenUsageTimeRetriever.getScreenOnTimeMs(0))
+ .thenReturn(60_000L);
+ when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0,
+ BatteryStats.SCREEN_BRIGHTNESS_DARK))
+ .thenReturn(10_000L);
+ when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0,
+ BatteryStats.SCREEN_BRIGHTNESS_MEDIUM))
+ .thenReturn(20_000L);
+ when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0,
+ BatteryStats.SCREEN_BRIGHTNESS_BRIGHT))
+ .thenReturn(30_000L);
+ when(mScreenUsageTimeRetriever.getScreenOnTimeMs(1))
+ .thenReturn(120_000L);
+ when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(0))
+ .thenReturn(180_000L);
+ when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(1))
+ .thenReturn(240_000L);
+ doAnswer(inv -> {
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback =
+ inv.getArgument(0);
+ callback.onUidTopActivityTime(APP_UID1, 3000);
+ callback.onUidTopActivityTime(APP_UID2, 5000);
+ callback.onUidTopActivityTime(ISOLATED_UID, 7000);
+ return null;
+ }).when(mScreenUsageTimeRetriever).retrieveTopActivityTimes(any(
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback.class));
+
+
+ PowerStats powerStats = collector.collectStats();
+
+ ScreenPowerStatsLayout layout = new ScreenPowerStatsLayout();
+ layout.fromExtras(powerStats.descriptor.extras);
+
+ // (45000 - 10000) / 3500
+ assertThat(layout.getConsumedEnergy(powerStats.stats, 0))
+ .isEqualTo(10_000);
+
+ assertThat(layout.getScreenOnDuration(powerStats.stats, 0))
+ .isEqualTo(60_000);
+ assertThat(layout.getBrightnessLevelDuration(powerStats.stats, 0,
+ BatteryStats.SCREEN_BRIGHTNESS_DARK))
+ .isEqualTo(10_000);
+ assertThat(layout.getBrightnessLevelDuration(powerStats.stats, 0,
+ BatteryStats.SCREEN_BRIGHTNESS_MEDIUM))
+ .isEqualTo(20_000);
+ assertThat(layout.getBrightnessLevelDuration(powerStats.stats, 0,
+ BatteryStats.SCREEN_BRIGHTNESS_BRIGHT))
+ .isEqualTo(30_000);
+ assertThat(layout.getScreenOnDuration(powerStats.stats, 1))
+ .isEqualTo(120_000);
+ assertThat(layout.getScreenDozeDuration(powerStats.stats, 0))
+ .isEqualTo(180_000);
+ assertThat(layout.getScreenDozeDuration(powerStats.stats, 1))
+ .isEqualTo(240_000);
+
+ assertThat(powerStats.uidStats.size()).isEqualTo(2);
+ // 3000 - 1000
+ assertThat(layout.getUidTopActivityDuration(powerStats.uidStats.get(APP_UID1)))
+ .isEqualTo(2000);
+ // (5000 - 2000) + 7000
+ assertThat(layout.getUidTopActivityDuration(powerStats.uidStats.get(APP_UID2)))
+ .isEqualTo(10000);
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java
new file mode 100644
index 0000000..9fde61a
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java
@@ -0,0 +1,287 @@
+/*
+ * 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.power.stats;
+
+import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.ScreenPowerStatsCollector.Injector;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.IntSupplier;
+
+public class ScreenPowerStatsProcessorTest {
+
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
+ @Rule(order = 1)
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setNumDisplays(2)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_AMBIENT, 0, 180.0)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_AMBIENT, 1, 360.0)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON, 0, 480.0)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON, 1, 720.0)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL, 0, 4800.0)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON, 1, 7200.0)
+ .initMeasuredEnergyStatsLocked();
+
+ private static final double PRECISION = 0.1;
+ private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
+ private static final int VOLTAGE_MV = 3500;
+
+ @Mock
+ private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ @Mock
+ private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever;
+
+ private final Injector mInjector = new Injector() {
+ @Override
+ public Handler getHandler() {
+ return mStatsRule.getHandler();
+ }
+
+ @Override
+ public Clock getClock() {
+ return mStatsRule.getMockClock();
+ }
+
+ @Override
+ public PowerStatsUidResolver getUidResolver() {
+ return new PowerStatsUidResolver();
+ }
+
+ @Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
+ public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+ return mConsumedEnergyRetriever;
+ }
+
+ @Override
+ public IntSupplier getVoltageSupplier() {
+ return () -> VOLTAGE_MV;
+ }
+
+ @Override
+ public int getDisplayCount() {
+ return 2;
+ }
+
+ @Override
+ public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() {
+ return mScreenUsageTimeRetriever;
+ }
+ };
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void processPowerStats_powerProfile() {
+ PowerComponentAggregatedPowerStats stats = collectAndAggregatePowerStats(false);
+
+ assertDevicePowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_ON, 195.5, 0);
+ assertDevicePowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0, 0.6);
+ assertDevicePowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_ON, 97.8, 0);
+ assertDevicePowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0, 0);
+
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_BATTERY, SCREEN_STATE_ON, 78.2);
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0);
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_OTHER, SCREEN_STATE_ON, 39.1);
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0);
+
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_BATTERY, SCREEN_STATE_ON, 117.3);
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0);
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_OTHER, SCREEN_STATE_ON, 58.7);
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0);
+ }
+
+ @Test
+ public void processPowerStats_energyConsumer() {
+ PowerComponentAggregatedPowerStats stats = collectAndAggregatePowerStats(true);
+
+ assertDevicePowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_ON, 261.9, 0);
+ assertDevicePowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0, 7.2);
+ assertDevicePowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_ON, 130.9, 0);
+ assertDevicePowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0, 0);
+
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_BATTERY, SCREEN_STATE_ON, 104.8);
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0);
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_OTHER, SCREEN_STATE_ON, 52.4);
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0);
+
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_BATTERY, SCREEN_STATE_ON, 157.1);
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0);
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_OTHER, SCREEN_STATE_ON, 78.6);
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0);
+ }
+
+ private PowerComponentAggregatedPowerStats collectAndAggregatePowerStats(
+ boolean energyConsumer) {
+ ScreenPowerStatsProcessor processor =
+ new ScreenPowerStatsProcessor(mStatsRule.getPowerProfile());
+
+ PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
+
+ ScreenPowerStatsCollector collector = new ScreenPowerStatsCollector(mInjector);
+ collector.setEnabled(true);
+
+ if (energyConsumer) {
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY))
+ .thenReturn(new int[]{77});
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77}))
+ .thenReturn(new long[]{10_000});
+ } else {
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY))
+ .thenReturn(new int[0]);
+ }
+
+ doAnswer(inv -> {
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback =
+ inv.getArgument(0);
+ callback.onUidTopActivityTime(APP_UID1, 1000);
+ callback.onUidTopActivityTime(APP_UID2, 2000);
+ return null;
+ }).when(mScreenUsageTimeRetriever).retrieveTopActivityTimes(any(
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback.class));
+
+ aggregatedStats.addPowerStats(collector.collectStats(), 1000);
+
+ if (energyConsumer) {
+ // 400 mAh represented as microWattSeconds
+ long energyUws = 400L * 3600 * VOLTAGE_MV;
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77}))
+ .thenReturn(new long[]{10_000 + energyUws});
+ }
+
+ when(mScreenUsageTimeRetriever.getScreenOnTimeMs(0))
+ .thenReturn(60_000L);
+ when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0,
+ BatteryStats.SCREEN_BRIGHTNESS_DARK))
+ .thenReturn(10_000L);
+ when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0,
+ BatteryStats.SCREEN_BRIGHTNESS_MEDIUM))
+ .thenReturn(20_000L);
+ when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0,
+ BatteryStats.SCREEN_BRIGHTNESS_BRIGHT))
+ .thenReturn(30_000L);
+ when(mScreenUsageTimeRetriever.getScreenOnTimeMs(1))
+ .thenReturn(120_000L);
+ when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(0))
+ .thenReturn(180_000L);
+ when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(1))
+ .thenReturn(240_000L);
+ doAnswer(inv -> {
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback =
+ inv.getArgument(0);
+ callback.onUidTopActivityTime(APP_UID1, 3000);
+ callback.onUidTopActivityTime(APP_UID2, 5000);
+ return null;
+ }).when(mScreenUsageTimeRetriever).retrieveTopActivityTimes(any(
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback.class));
+
+ aggregatedStats.setState(STATE_POWER, POWER_STATE_BATTERY, 201_000);
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 601_000);
+
+ // Slightly larger than 600_000 total screen time, to simulate a sight race
+ // between state changes and power stats collection
+ aggregatedStats.addPowerStats(collector.collectStats(), 612_000);
+
+ aggregatedStats.getConfig().getProcessor().finish(aggregatedStats, 180_000);
+ return aggregatedStats;
+ }
+
+ private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
+ ScreenPowerStatsProcessor processor) {
+ AggregatedPowerStatsConfig.PowerComponent config =
+ new AggregatedPowerStatsConfig.PowerComponent(
+ BatteryConsumer.POWER_COMPONENT_SCREEN)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_SCREEN)
+ .setProcessor(processor);
+
+ PowerComponentAggregatedPowerStats aggregatedStats =
+ new PowerComponentAggregatedPowerStats(
+ new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
+
+ aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+
+ return aggregatedStats;
+ }
+
+ private void assertDevicePowerEstimate(PowerComponentAggregatedPowerStats aggregatedStats,
+ int powerState, int screenState, double expectedScreenPowerEstimate,
+ double expectedDozePowerEstimate) {
+ PowerStats.Descriptor descriptor = aggregatedStats.getPowerStatsDescriptor();
+ ScreenPowerStatsLayout layout = new ScreenPowerStatsLayout(descriptor);
+ long[] stats = new long[descriptor.statsArrayLength];
+ aggregatedStats.getDeviceStats(stats, new int[]{powerState, screenState});
+ assertThat(layout.getDevicePowerEstimate(stats)).isWithin(PRECISION)
+ .of(expectedScreenPowerEstimate);
+ assertThat(layout.getScreenDozePowerEstimate(stats)).isWithin(PRECISION)
+ .of(expectedDozePowerEstimate);
+ }
+
+ private void assertUidPowerEstimate(PowerComponentAggregatedPowerStats aggregatedStats, int uid,
+ int powerState, int screenState, double expectedScreenPowerEstimate) {
+ PowerStats.Descriptor descriptor = aggregatedStats.getPowerStatsDescriptor();
+ ScreenPowerStatsLayout layout = new ScreenPowerStatsLayout(descriptor);
+ long[] stats = new long[descriptor.uidStatsArrayLength];
+ aggregatedStats.getUidStats(stats, uid,
+ new int[]{powerState, screenState, PROCESS_STATE_ANY});
+ assertThat(layout.getUidPowerEstimate(stats)).isWithin(PRECISION)
+ .of(expectedScreenPowerEstimate);
+ }
+}
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/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
index f92eaab..c1b3929 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
@@ -18,13 +18,13 @@
import static com.android.server.accessibility.Flags.FLAG_ENABLE_A11Y_CHECKER_LOGGING;
import static com.android.server.accessibility.a11ychecker.AccessibilityCheckerConstants.MIN_DURATION_BETWEEN_CHECKS;
+import static com.android.server.accessibility.a11ychecker.TestUtils.QUALIFIED_TEST_ACTIVITY_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_CLASS_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_DEFAULT_BROWSER;
import static com.android.server.accessibility.a11ychecker.TestUtils.createAtom;
import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps;
-import static com.android.server.accessibility.a11ychecker.TestUtils.getTestAccessibilityEvent;
import static com.google.common.truth.Truth.assertThat;
@@ -114,7 +114,7 @@
Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
mAccessibilityCheckerManager.maybeRunA11yChecker(
- List.of(mockNodeInfo1, mockNodeInfo2), getTestAccessibilityEvent(),
+ List.of(mockNodeInfo1, mockNodeInfo2), QUALIFIED_TEST_ACTIVITY_NAME,
new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0);
@@ -139,7 +139,7 @@
Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
mAccessibilityCheckerManager.maybeRunA11yChecker(
- List.of(mockNodeInfo), getTestAccessibilityEvent(),
+ List.of(mockNodeInfo), QUALIFIED_TEST_ACTIVITY_NAME,
new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0);
@@ -160,7 +160,7 @@
Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
mAccessibilityCheckerManager.maybeRunA11yChecker(
- List.of(mockNodeInfo, mockNodeInfoDuplicate), getTestAccessibilityEvent(),
+ List.of(mockNodeInfo, mockNodeInfoDuplicate), QUALIFIED_TEST_ACTIVITY_NAME,
new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
index 141f174..5b4e72e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
@@ -16,11 +16,13 @@
package com.android.server.accessibility.a11ychecker;
+import static com.android.server.accessibility.a11ychecker.TestUtils.QUALIFIED_TEST_ACTIVITY_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_CLASS_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.createAtom;
import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps;
-import static com.android.server.accessibility.a11ychecker.TestUtils.getTestAccessibilityEvent;
import static com.google.common.truth.Truth.assertThat;
@@ -28,7 +30,6 @@
import android.content.ComponentName;
import android.content.pm.PackageManager;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.test.runner.AndroidJUnit4;
@@ -138,11 +139,15 @@
}
@Test
- public void getActivityName_hasWindowStateChangedEvent_returnsActivityName() {
- AccessibilityEvent accessibilityEvent = getTestAccessibilityEvent();
-
+ public void getActivityName_hasValidActivityClassName_returnsActivityName() {
assertThat(AccessibilityCheckerUtils.getActivityName(mMockPackageManager,
- accessibilityEvent)).isEqualTo("MainActivity");
+ TEST_APP_PACKAGE_NAME, QUALIFIED_TEST_ACTIVITY_NAME)).isEqualTo(TEST_ACTIVITY_NAME);
+ }
+
+ @Test
+ public void getActivityName_hasInvalidActivityClassName_returnsActivityName() {
+ assertThat(AccessibilityCheckerUtils.getActivityName(mMockPackageManager,
+ TEST_APP_PACKAGE_NAME, "com.NonActivityClass")).isEmpty();
}
// Makes sure the AccessibilityHierarchyCheck class to enum mapping is up to date with the
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
index ec1a255..acf64b6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility.a11ychecker;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.when;
@@ -26,6 +27,7 @@
import android.content.pm.PackageManager;
import android.view.accessibility.AccessibilityEvent;
+import org.mockito.AdditionalMatchers;
import org.mockito.Mockito;
public class TestUtils {
@@ -46,8 +48,11 @@
ComponentName testActivityComponentName = new ComponentName(TEST_APP_PACKAGE_NAME,
QUALIFIED_TEST_ACTIVITY_NAME);
- when(mockPackageManager.getActivityInfo(testActivityComponentName, 0))
+ when(mockPackageManager.getActivityInfo(eq(testActivityComponentName), eq(0)))
.thenReturn(testActivityInfo);
+ when(mockPackageManager.getActivityInfo(
+ AdditionalMatchers.not(eq(testActivityComponentName)), eq(0)))
+ .thenThrow(PackageManager.NameNotFoundException.class);
when(mockPackageManager.getPackageInfo(TEST_APP_PACKAGE_NAME, 0))
.thenReturn(createPackageInfo(TEST_APP_PACKAGE_NAME, TEST_APP_VERSION_CODE,
testActivityInfo));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index b07940a..d7bae45 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1044,6 +1044,8 @@
AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
mZenModeHelper.mAudioManager = mAudioManager;
setupZenConfig();
+ mTestableLooper.processAllMessages();
+ reset(mAudioManager);
// Turn manual zen mode on
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
@@ -1063,6 +1065,44 @@
}
@Test
+ public void testSetConfig_updatesAudioForSequentialChangesToZenMode() {
+ AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
+ mZenModeHelper.mAudioManager = mAudioManager;
+ setupZenConfig();
+ mTestableLooper.processAllMessages();
+ reset(mAudioManager);
+
+ // Turn manual zen mode on
+ mZenModeHelper.setManualZenMode(
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ null,
+ UPDATE_ORIGIN_APP,
+ null,
+ "test",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.setManualZenMode(
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ null,
+ UPDATE_ORIGIN_APP,
+ null,
+ "test",
+ CUSTOM_PKG_UID);
+
+ // audio manager shouldn't do anything until the handler processes its messages
+ verify(mAudioManager, never()).updateRingerModeAffectedStreamsInternal();
+
+ // now process the looper's messages
+ mTestableLooper.processAllMessages();
+
+ // Expect calls to audio manager
+ verify(mAudioManager, times(2)).updateRingerModeAffectedStreamsInternal();
+ verify(mAudioManager, times(1)).setRingerModeInternal(anyInt(), anyString());
+
+ // called during applyZenToRingerMode(), which should be true since zen changed
+ verify(mAudioManager, atLeastOnce()).getRingerModeInternal();
+ }
+
+ @Test
public void testParcelConfig() {
mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS
| PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
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..bea6917 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 {
+ // Grant 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,42 @@
}
@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 {
+ // Grant permission to vibrate with vendor effects
+ grantPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS);
+
+ 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_LIGHT);
+ assertThat(scaled.getLinearScale()).isEqualTo(0.4f);
+ }
+
+ @Test
public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
mockVibrators(1, 2);
VibratorManagerService service = createSystemReadyService();
@@ -2729,7 +2812,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/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
index 68ccb31..14fbbe4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -191,8 +191,8 @@
verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
- final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
- cycleThroughStop ? ON_STOP : ON_PAUSE);
+ final RefreshCallbackItem refreshCallbackItem =
+ new RefreshCallbackItem(mActivity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(mActivity.token,
/* isForward */ false, /* shouldSendCompatFakeFocus */ false);
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..220248c 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;
@@ -123,6 +126,10 @@
.isCameraActive(any(ActivityRecord.class), anyBoolean());
}
+ void setDisplayNaturalOrientation(@Configuration.Orientation int naturalOrientation) {
+ doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
+ }
+
@NonNull
ActivityRecord top() {
return mActivityStack.top();
@@ -186,10 +193,36 @@
mDisplayContent.setIgnoreOrientationRequest(enabled);
}
+ void setTopTaskInMultiWindowMode(boolean inMultiWindowMode) {
+ doReturn(inMultiWindowMode).when(mTaskStack.top())
+ .inMultiWindowMode();
+ }
+
void setTopActivityAsEmbedded(boolean embedded) {
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();
}
@@ -201,20 +234,21 @@
void createNewDisplay() {
mDisplayContent = new TestDisplayContent.Builder(mAtm, mDisplayWidth, mDisplayHeight)
.build();
+ spyOn(mDisplayContent);
spyOnAppCompatCameraPolicy();
}
void createNewTask() {
final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
.setDisplay(mDisplayContent).build();
- mTaskStack.push(newTask);
+ pushTask(newTask);
}
void createNewTaskWithBaseActivity() {
final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
.setCreateActivity(true)
.setDisplay(mDisplayContent).build();
- mTaskStack.push(newTask);
+ pushTask(newTask);
pushActivity(newTask.getTopNonFinishingActivity());
}
@@ -401,12 +435,20 @@
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.mAppCompatController.getAppCompatResizeOverrides());
spyOn(activity.mLetterboxUiController);
}
+ private void pushTask(@NonNull Task task) {
+ spyOn(task);
+ mTaskStack.push(task);
+ }
+
private void spyOnAppCompatCameraPolicy() {
spyOn(mDisplayContent.mAppCompatCameraPolicy);
if (mDisplayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
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/AppCompatOrientationOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
index 634453f..6c0d8c4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
@@ -16,6 +16,10 @@
package com.android.server.wm;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
+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.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -158,6 +162,72 @@
});
}
+ @Test
+ @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+ public void testShouldUseDisplayLandscapeNaturalOrientation_override_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.setDisplayNaturalOrientation(ORIENTATION_LANDSCAPE);
+ a.setIgnoreOrientationRequest(true);
+ a.createActivityWithComponent();
+ });
+ robot.checkShouldUseDisplayLandscapeNaturalOrientation(/* expected */ true);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+ public void testShouldUseDisplayLandscapeNaturalOrientation_falseProperty_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE);
+ robot.applyOnActivity((a) -> {
+ a.setDisplayNaturalOrientation(ORIENTATION_LANDSCAPE);
+ a.setIgnoreOrientationRequest(true);
+ a.createActivityWithComponent();
+ });
+ robot.checkShouldUseDisplayLandscapeNaturalOrientation(/* expected */ false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+ public void testShouldUseDisplayLandscapeNaturalOrientation_portrait_isFalse() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.setDisplayNaturalOrientation(ORIENTATION_PORTRAIT);
+ a.setIgnoreOrientationRequest(true);
+ a.createActivityWithComponent();
+ });
+ robot.checkShouldUseDisplayLandscapeNaturalOrientation(/* expected */ false);
+ });
+ }
+ @Test
+ @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+ public void testShouldUseDisplayLandscapeNaturalOrientation_noIgnoreRequest_isFalse() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.setDisplayNaturalOrientation(ORIENTATION_LANDSCAPE);
+ a.setIgnoreOrientationRequest(false);
+ a.createActivityWithComponent();
+ });
+ robot.checkShouldUseDisplayLandscapeNaturalOrientation(/* expected */ false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+ public void testShouldUseDisplayLandscapeNaturalOrientation_inMultiWindowMode_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.setDisplayNaturalOrientation(ORIENTATION_LANDSCAPE);
+ a.setIgnoreOrientationRequest(true);
+ a.createActivityWithComponent();
+ a.setTopTaskInMultiWindowMode(/* inMultiWindowMode */ true);
+ });
+ robot.checkShouldUseDisplayLandscapeNaturalOrientation(/* expected */ false);
+ });
+ }
+
/**
* Runs a test scenario providing a Robot.
*/
@@ -215,6 +285,11 @@
}
}
+ void checkShouldUseDisplayLandscapeNaturalOrientation(boolean expected) {
+ assertEquals(expected,
+ getTopOrientationOverrides().shouldUseDisplayLandscapeNaturalOrientation());
+ }
+
private AppCompatOrientationOverrides getTopOrientationOverrides() {
return activity().top().mAppCompatController.getAppCompatOverrides()
.getAppCompatOrientationOverrides();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
new file mode 100644
index 0000000..8fc1a77
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.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.FORCE_NON_RESIZE_APP;
+import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+
+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;
+
+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;
+
+/**
+ * Test class for {@link AppCompatResizeOverrides}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatResizeOverridesTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatResizeOverridesTest extends WindowTestsBase {
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({FORCE_RESIZE_APP})
+ public void testShouldOverrideForceResizeApp_overrideEnabled_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceResizeApp(/* expected */ true);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({FORCE_RESIZE_APP})
+ public void testShouldOverrideForceResizeApp_propertyTrue_overrideEnabled_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceResizeApp(/* expected */ true);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges({FORCE_RESIZE_APP})
+ public void testShouldOverrideForceResizeApp_propertyTrue_overrideDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceResizeApp(/* expected */ false);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges({FORCE_RESIZE_APP})
+ public void testShouldOverrideForceResizeApp_overrideDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceResizeApp(/* expected */ false);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({FORCE_RESIZE_APP})
+ public void testShouldOverrideForceResizeApp_propertyFalse_overrideEnabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceResizeApp(/* expected */ false);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges({FORCE_RESIZE_APP})
+ public void testShouldOverrideForceResizeApp_propertyFalse_noOverride_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceResizeApp(/* expected */ false);
+ });
+ }
+
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testShouldOverrideForceNonResizeApp_overrideEnabled_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceNonResizeApp(/* expected */ true);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testShouldOverrideForceNonResizeApp_propertyTrue_overrideEnabled_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceNonResizeApp(/* expected */ true);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testShouldOverrideForceNonResizeApp_propertyTrue_overrideDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceNonResizeApp(/* expected */ false);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testShouldOverrideForceNonResizeApp_overrideDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceNonResizeApp(/* expected */ false);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testShouldOverrideForceNonResizeApp_propertyFalse_overrideEnabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceNonResizeApp(/* expected */ false);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testShouldOverrideForceNonResizeApp_propertyFalse_noOverride_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceNonResizeApp(/* expected */ false);
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<ResizeOverridesRobotTest> consumer) {
+ spyOn(mWm.mAppCompatConfiguration);
+ final ResizeOverridesRobotTest robot = new ResizeOverridesRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class ResizeOverridesRobotTest extends AppCompatRobotBase {
+
+ ResizeOverridesRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ }
+
+
+ void checkShouldOverrideForceResizeApp(boolean expected) {
+ Assert.assertEquals(expected, activity().top().mAppCompatController
+ .getAppCompatResizeOverrides().shouldOverrideForceResizeApp());
+ }
+
+ void checkShouldOverrideForceNonResizeApp(boolean expected) {
+ Assert.assertEquals(expected, activity().top().mAppCompatController
+ .getAppCompatResizeOverrides().shouldOverrideForceNonResizeApp());
+ }
+ }
+
+}
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/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index c43f414..f2592d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -307,8 +307,8 @@
verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
- final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
- cycleThroughStop ? ON_STOP : ON_PAUSE);
+ final RefreshCallbackItem refreshCallbackItem =
+ new RefreshCallbackItem(mActivity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(mActivity.token,
/* isForward */ false, /* shouldSendCompatFakeFocus */ false);
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/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 12bc3ed..2dea6ba 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -613,8 +613,8 @@
verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
- final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
- cycleThroughStop ? ON_STOP : ON_PAUSE);
+ final RefreshCallbackItem refreshCallbackItem =
+ new RefreshCallbackItem(mActivity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(mActivity.token,
/* isForward */ false, /* shouldSendCompatFakeFocus */ false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 8cdb574..1072ef0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -152,8 +152,8 @@
// Use a new TestIWindow so we don't collect events for other windows
final WindowState window = createWindow(
null, TYPE_BASE_APPLICATION, activity, name, ownerId, false, new TestIWindow());
- window.mInputChannel = new InputChannel();
- window.mInputChannelToken = window.mInputChannel.getToken();
+ InputChannel channel = new InputChannel();
+ window.openInputChannel(channel);
window.mHasSurface = true;
mWm.mWindowMap.put(window.mClient.asBinder(), window);
mWm.mInputToWindowMap.put(window.mInputChannelToken, window);
@@ -178,8 +178,8 @@
TEST_PID, TEST_UID);
mWindow = createDropTargetWindow("Drag test window", 0);
doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
- when(mWm.mInputManager.startDragAndDrop(any(InputChannel.class),
- any(InputChannel.class))).thenReturn(true);
+ when(mWm.mInputManager.startDragAndDrop(any(IBinder.class),
+ any(IBinder.class))).thenReturn(true);
mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
}
@@ -707,8 +707,7 @@
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- assertTrue(mWm.mInputManager.startDragAndDrop(new InputChannel(),
- new InputChannel()));
+ assertTrue(mWm.mInputManager.startDragAndDrop(new Binder(), new Binder()));
mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient,
flag, surface, 0, 0, 0, 0, 0, 0, 0, data);
assertNotNull(mToken);
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..e2c0f6c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -16,16 +16,7 @@
package com.android.server.wm;
-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;
@@ -38,15 +29,12 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.Property;
import android.content.res.Resources;
import android.graphics.Rect;
import android.platform.test.annotations.DisableFlags;
@@ -64,9 +52,6 @@
import com.android.internal.R;
import com.android.window.flags.Flags;
-import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
-import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -311,238 +296,6 @@
return mainWindow;
}
- // shouldUseDisplayLandscapeNaturalOrientation
-
- @Test
- @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
- public void testShouldUseDisplayLandscapeNaturalOrientation_override_returnsTrue() {
- prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
- assertTrue(mController.shouldUseDisplayLandscapeNaturalOrientation());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
- public void testShouldUseDisplayLandscapeNaturalOrientation_overrideAndFalseProperty_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE, /* value */ false);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
- assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
- public void testShouldUseDisplayLandscapeNaturalOrientation_portraitNaturalOrientation_returnsFalse() {
- prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
- doReturn(ORIENTATION_PORTRAIT).when(mDisplayContent).getNaturalOrientation();
-
- assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
- public void testShouldUseDisplayLandscapeNaturalOrientation_disabledIgnoreOrientationRequest_returnsFalse() {
- prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
- mDisplayContent.setIgnoreOrientationRequest(false);
-
- assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
- public void testShouldUseDisplayLandscapeNaturalOrientation_inMultiWindowMode_returnsFalse() {
- prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
-
- spyOn(mTask);
- doReturn(true).when(mTask).inMultiWindowMode();
-
- assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation());
- }
-
- @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);
-
- assertTrue(mController.shouldOverrideForceResizeApp());
- }
-
- @Test
- @EnableCompatChanges({FORCE_RESIZE_APP})
- public void testshouldOverrideForceResizeApp_propertyTrue_overrideEnabled_returnsTrue()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mController.shouldOverrideForceResizeApp());
- }
-
- @Test
- @DisableCompatChanges({FORCE_RESIZE_APP})
- public void testshouldOverrideForceResizeApp_propertyTrue_overrideDisabled_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceResizeApp());
- }
-
- @Test
- @DisableCompatChanges({FORCE_RESIZE_APP})
- public void testshouldOverrideForceResizeApp_overrideDisabled_returnsFalse() {
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceResizeApp());
- }
-
- @Test
- @EnableCompatChanges({FORCE_RESIZE_APP})
- public void testshouldOverrideForceResizeApp_propertyFalse_overrideEnabled_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false);
-
- mActivity = setUpActivityWithComponent();
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceResizeApp());
- }
-
- @Test
- @DisableCompatChanges({FORCE_RESIZE_APP})
- public void testshouldOverrideForceResizeApp_propertyFalse_noOverride_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false);
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceResizeApp());
- }
-
- @Test
- @EnableCompatChanges({FORCE_NON_RESIZE_APP})
- public void testshouldOverrideForceNonResizeApp_overrideEnabled_returnsTrue() {
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mController.shouldOverrideForceNonResizeApp());
- }
-
- @Test
- @EnableCompatChanges({FORCE_NON_RESIZE_APP})
- public void testshouldOverrideForceNonResizeApp_propertyTrue_overrideEnabled_returnsTrue()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mController.shouldOverrideForceNonResizeApp());
- }
-
- @Test
- @DisableCompatChanges({FORCE_NON_RESIZE_APP})
- public void testshouldOverrideForceNonResizeApp_propertyTrue_overrideDisabled_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceNonResizeApp());
- }
-
- @Test
- @DisableCompatChanges({FORCE_NON_RESIZE_APP})
- public void testshouldOverrideForceNonResizeApp_overrideDisabled_returnsFalse() {
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceNonResizeApp());
- }
-
- @Test
- @EnableCompatChanges({FORCE_NON_RESIZE_APP})
- public void testshouldOverrideForceNonResizeApp_propertyFalse_overrideEnabled_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false);
-
- mActivity = setUpActivityWithComponent();
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceNonResizeApp());
- }
-
- @Test
- @DisableCompatChanges({FORCE_NON_RESIZE_APP})
- public void testshouldOverrideForceNonResizeApp_propertyFalse_noOverride_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false);
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceNonResizeApp());
- }
-
@Test
public void testgetFixedOrientationLetterboxAspectRatio_splitScreenAspectEnabled() {
doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration)
@@ -672,20 +425,6 @@
verify(mAppCompatConfiguration).getIsEducationEnabled();
}
- private void mockThatProperty(String propertyName, boolean value) throws Exception {
- Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
- /* className */ "");
- PackageManager pm = mWm.mContext.getPackageManager();
- spyOn(pm);
- doReturn(property).when(pm).getProperty(eq(propertyName), anyString());
- }
-
- private void prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation() {
- spyOn(mDisplayContent);
- doReturn(ORIENTATION_LANDSCAPE).when(mDisplayContent).getNaturalOrientation();
- mDisplayContent.setIgnoreOrientationRequest(true);
- }
-
private ActivityRecord setUpActivityWithComponent() {
mDisplayContent = new TestDisplayContent
.Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index 280fe4c..34f7ebb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -66,6 +66,7 @@
import org.junit.Test;
import org.junit.rules.TestName;
+import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -131,7 +132,7 @@
assertTrue("Failed to wait for transaction to get committed",
countDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS));
assertTrue("Failed to wait for stable geometry",
- waitForStableWindowGeometry(WAIT_TIME_S, TimeUnit.SECONDS));
+ waitForStableWindowGeometry(Duration.ofSeconds(WAIT_TIME_S)));
ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC)
.setCaptureSecureLayers(true)
@@ -212,7 +213,7 @@
assertTrue("Failed to wait for transaction to get committed",
countDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS));
assertTrue("Failed to wait for stable geometry",
- waitForStableWindowGeometry(WAIT_TIME_S, TimeUnit.SECONDS));
+ waitForStableWindowGeometry(Duration.ofSeconds(WAIT_TIME_S)));
ScreenshotHardwareBuffer[] screenCapture = new ScreenshotHardwareBuffer[1];
Bitmap screenshot = null;
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..ed93a8c 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;
@@ -789,7 +788,7 @@
// Change the fixed orientation.
mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
assertTrue(mActivity.isRelaunching());
- assertTrue(mActivity.mLetterboxUiController
+ assertTrue(mActivity.mAppCompatController.getAppCompatOrientationOverrides()
.getIsRelaunchingAfterRequestedOrientationChanged());
assertFitted();
@@ -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/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index d5d2847..b92af87 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -77,6 +77,7 @@
import android.view.SurfaceControl;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.internal.os.BackgroundThread;
import com.android.server.AnimationThread;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
@@ -553,6 +554,9 @@
// This is a different handler object than the wm.mAnimationHandler above.
waitHandlerIdle(AnimationThread.getHandler());
waitHandlerIdle(SurfaceAnimationThread.getHandler());
+ // Some binder calls are posted to BackgroundThread.getHandler(), we should wait for them
+ // to finish to run next test.
+ waitHandlerIdle(BackgroundThread.getHandler());
}
static void waitHandlerIdle(Handler handler) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index a1ac02a..a232ff0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -46,6 +46,7 @@
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.google.common.truth.Truth.assertThat;
@@ -78,6 +79,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
@@ -2031,6 +2033,47 @@
task.getTaskInfo().appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode);
}
+ @Test
+ public void testUpdateTaskDescriptionOnReparent() {
+ final Task rootTask1 = createTask(mDisplayContent);
+ final Task rootTask2 = createTask(mDisplayContent);
+ final Task childTask = createTaskInRootTask(rootTask1, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, childTask);
+ final String testLabel = "test_task_description_label";
+ final ActivityManager.TaskDescription td = new ActivityManager.TaskDescription(testLabel);
+ activity.setTaskDescription(td);
+
+ // Ensure the td is set for the original root task
+ assertEquals(testLabel, rootTask1.getTaskDescription().getLabel());
+ assertNull(rootTask2.getTaskDescription().getLabel());
+
+ childTask.reparent(rootTask2, POSITION_TOP, false /* moveParents */, "reparent");
+
+ // Ensure the td is set for the new root task
+ assertEquals(testLabel, rootTask2.getTaskDescription().getLabel());
+ }
+
+ @Test
+ public void testUpdateTaskDescriptionOnReorder() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task);
+ final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task);
+ final ActivityManager.TaskDescription td1 = new ActivityManager.TaskDescription();
+ td1.setBackgroundColor(Color.RED);
+ activity1.setTaskDescription(td1);
+ final ActivityManager.TaskDescription td2 = new ActivityManager.TaskDescription();
+ td2.setBackgroundColor(Color.BLUE);
+ activity2.setTaskDescription(td2);
+
+ // Ensure the td is set for the original root task
+ assertEquals(Color.BLUE, task.getTaskDescription().getBackgroundColor());
+
+ task.positionChildAt(POSITION_TOP, activity1, false /* includeParents */);
+
+ // Ensure the td is set for the original root task
+ assertEquals(Color.RED, task.getTaskDescription().getBackgroundColor());
+ }
+
private Task getTestTask() {
return new TaskBuilder(mSupervisor).setCreateActivity(true).build();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
index f1d84cf..529e9b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
@@ -49,6 +49,7 @@
import org.junit.Test;
import org.junit.rules.TestName;
+import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -144,7 +145,7 @@
}
}
return false;
- }, TIMEOUT_S, TimeUnit.SECONDS);
+ }, Duration.ofSeconds(TIMEOUT_S));
assertAndDumpWindowState(TAG, "Failed to find window or was not marked trusted",
foundTrusted[0]);
@@ -209,7 +210,7 @@
}
}
return foundTrusted[0] && foundTrusted[1];
- }, TIMEOUT_S, TimeUnit.SECONDS);
+ }, Duration.ofSeconds(TIMEOUT_S));
if (!foundTrusted[0] || !foundTrusted[1]) {
CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, mName.getMethodName());
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) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 61698db..0468f48 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9992,6 +9992,51 @@
@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool";
+ /** @hide */
+ @IntDef({
+ CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC,
+ CARRIER_ROAMING_NTN_CONNECT_MANUAL,
+ })
+ public @interface CARRIER_ROAMING_NTN_CONNECT_TYPE {}
+
+ /**
+ * Device can connect to carrier roaming non-terrestrial network automatically.
+ * @hide
+ */
+ public static final int CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC = 0;
+ /**
+ * Device can connect to carrier roaming non-terrestrial network only if user manually triggers
+ * satellite connection.
+ * @hide
+ */
+ public static final int CARRIER_ROAMING_NTN_CONNECT_MANUAL = 1;
+ /**
+ * Indicates carrier roaming non-terrestrial network connect type that the device can use to
+ * perform satellite communication.
+ * If this key is set to CARRIER_ROAMING_NTN_CONNECT_MANUAL then connect button will be
+ * displayed to user when the device is eligible to use carrier roaming
+ * non-terrestrial network.
+ * @hide
+ */
+ public static final String KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT =
+ "carrier_roaming_ntn_connect_type_int";
+
+ /**
+ * The carrier roaming non-terrestrial network hysteresis time in seconds.
+ *
+ * If the device supports P2P satellite messaging which is defined by
+ * {@link CarrierConfigManager#KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE}
+ * and the device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi,
+ * then hysteresis timer defined by this key will start.
+ * After the timer is expired, device is marked as eligible for satellite communication.
+ *
+ * The default value is 180 seconds.
+ *
+ * @hide
+ */
+ public static final String KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT =
+ "carrier_supported_satellite_notification_hysteresis_sec_int";
+
/**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
* the default APN (i.e. internet) will be used for tethering.
@@ -11150,6 +11195,8 @@
sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
(int) TimeUnit.SECONDS.toMillis(30));
sDefaults.putBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL, false);
+ sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 0);
+ sDefaults.putInt(KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT, 180);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, false);
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
index 36cbf1a..365a0ea 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
@@ -27,15 +27,6 @@
where things are arranged differently and to circle back up to the top once we reach the
bottom. -->
- <!-- View used for testing sourceRectHint. -->
- <View
- android:id="@+id/source_rect"
- android:layout_width="320dp"
- android:layout_height="180dp"
- android:visibility="gone"
- android:background="@android:color/holo_green_light"
- />
-
<Button
android:id="@+id/enter_pip"
android:layout_width="wrap_content"
@@ -122,12 +113,11 @@
android:onClick="onRatioSelected"/>
</RadioGroup>
- <Button
+ <CheckBox
android:id="@+id/set_source_rect_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="Set SourceRectHint"
- android:onClick="setSourceRectHint"/>
+ android:text="Set SourceRectHint"/>
<TextView
android:layout_width="wrap_content"
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
index 27eb5a0..13d7f7f 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
@@ -43,10 +43,10 @@
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Rational;
import android.view.View;
-import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.widget.CheckBox;
@@ -70,7 +70,7 @@
*/
private static final String TITLE_STATE_PAUSED = "TestApp media is paused";
- private static final Rational RATIO_DEFAULT = null;
+ private static final Rational RATIO_DEFAULT = new Rational(16, 9);
private static final Rational RATIO_SQUARE = new Rational(1, 1);
private static final Rational RATIO_WIDE = new Rational(2, 1);
private static final Rational RATIO_TALL = new Rational(1, 2);
@@ -88,8 +88,7 @@
"com.android.wm.shell.flicker.testapp.ASPECT_RATIO";
private final PictureInPictureParams.Builder mPipParamsBuilder =
- new PictureInPictureParams.Builder()
- .setAspectRatio(RATIO_DEFAULT);
+ new PictureInPictureParams.Builder();
private MediaSession mMediaSession;
private final PlaybackState.Builder mPlaybackStateBuilder = new PlaybackState.Builder()
.setActions(ACTION_PLAY | ACTION_PAUSE | ACTION_STOP)
@@ -139,6 +138,9 @@
}
};
+ private Rational mAspectRatio = RATIO_DEFAULT;
+ private boolean mEnableSourceRectHint;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -156,6 +158,14 @@
findViewById(R.id.media_session_stop)
.setOnClickListener(v -> updateMediaSessionState(STATE_STOPPED));
+ final CheckBox setSourceRectHintCheckBox = findViewById(R.id.set_source_rect_hint);
+ setSourceRectHintCheckBox.setOnCheckedChangeListener((v, isChecked) -> {
+ if (mEnableSourceRectHint != isChecked) {
+ mEnableSourceRectHint = isChecked;
+ updateSourceRectHint();
+ }
+ });
+
mMediaSession = new MediaSession(this, "WMShell_TestApp");
mMediaSession.setPlaybackState(mPlaybackStateBuilder.build());
mMediaSession.setCallback(new MediaSession.Callback() {
@@ -250,47 +260,64 @@
}
}
+ private void updateSourceRectHint() {
+ if (!mEnableSourceRectHint) return;
+ // Similar to PipUtils#getEnterPipWithOverlaySrcRectHint, crop the display bounds
+ // as source rect hint based on the current aspect ratio.
+ final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+ final Rect displayBounds = new Rect(0, 0,
+ displayMetrics.widthPixels, displayMetrics.heightPixels);
+ final Rect sourceRectHint = getEnterPipWithOverlaySrcRectHint(
+ displayBounds, mAspectRatio.floatValue());
+ mPipParamsBuilder
+ .setAspectRatio(mAspectRatio)
+ .setSourceRectHint(sourceRectHint);
+ setPictureInPictureParams(mPipParamsBuilder.build());
+ }
+
/**
- * Adds a temporary view used for testing sourceRectHint.
- *
+ * Crop a Rect matches the aspect ratio and pivots at the center point.
+ * This is a counterpart of {@link PipUtils#getEnterPipWithOverlaySrcRectHint}
*/
- public void setSourceRectHint(View v) {
- View rectView = findViewById(R.id.source_rect);
- if (rectView != null) {
- rectView.setVisibility(View.VISIBLE);
- rectView.getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- Rect boundingRect = new Rect();
- rectView.getGlobalVisibleRect(boundingRect);
- mPipParamsBuilder.setSourceRectHint(boundingRect);
- setPictureInPictureParams(mPipParamsBuilder.build());
- rectView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- }
- });
- rectView.invalidate(); // changing the visibility, invalidating to redraw the view
+ private Rect getEnterPipWithOverlaySrcRectHint(Rect appBounds, float aspectRatio) {
+ final float appBoundsAspectRatio = appBounds.width() / (float) appBounds.height();
+ final int width, height;
+ int left = appBounds.left;
+ int top = appBounds.top;
+ if (appBoundsAspectRatio < aspectRatio) {
+ width = appBounds.width();
+ height = (int) (width / aspectRatio);
+ top = appBounds.top + (appBounds.height() - height) / 2;
+ } else {
+ height = appBounds.height();
+ width = (int) (height * aspectRatio);
+ left = appBounds.left + (appBounds.width() - width) / 2;
}
+ return new Rect(left, top, left + width, top + height);
}
public void onRatioSelected(View v) {
switch (v.getId()) {
case R.id.ratio_default:
- mPipParamsBuilder.setAspectRatio(RATIO_DEFAULT);
+ mAspectRatio = RATIO_DEFAULT;
break;
case R.id.ratio_square:
- mPipParamsBuilder.setAspectRatio(RATIO_SQUARE);
+ mAspectRatio = RATIO_SQUARE;
break;
case R.id.ratio_wide:
- mPipParamsBuilder.setAspectRatio(RATIO_WIDE);
+ mAspectRatio = RATIO_WIDE;
break;
case R.id.ratio_tall:
- mPipParamsBuilder.setAspectRatio(RATIO_TALL);
+ mAspectRatio = RATIO_TALL;
break;
}
+ setPictureInPictureParams(mPipParamsBuilder.setAspectRatio(mAspectRatio).build());
+ if (mEnableSourceRectHint) {
+ updateSourceRectHint();
+ }
}
private void updateMediaSessionState(int newState) {
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 8d1fc50..d32cedb 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -40,7 +40,7 @@
import com.android.cts.input.DebugInputRule
import com.android.cts.input.UinputTouchScreen
-import java.util.concurrent.TimeUnit
+import java.time.Duration
import org.junit.After
import org.junit.Assert.assertEquals
@@ -193,6 +193,6 @@
val flags = " -W -n "
val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity"
instrumentation.uiAutomation.executeShellCommand(startCmd)
- waitForStableWindowGeometry(5L, TimeUnit.SECONDS)
+ waitForStableWindowGeometry(Duration.ofSeconds(5))
}
}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
index 4c531b8..a4085e5 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
@@ -23,6 +23,7 @@
resource_dirs: ["res"],
libs: ["android.test.runner"],
static_libs: [
+ "androidx.core_core",
"androidx.test.ext.junit",
"androidx.test.rules",
"compatibility-device-util-axt",
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 2df9418..45bf8e3 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -690,7 +690,9 @@
resource_format = item_iter->second.format;
}
- if (!ParseItem(parser, out_resource, resource_format)) {
+ // Don't bother parsing the item if it is behind a disabled flag
+ if (out_resource->flag_status != FlagStatus::Disabled &&
+ !ParseItem(parser, out_resource, resource_format)) {
return false;
}
return true;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index b59b165..2e6ad13 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -69,8 +69,13 @@
return TestParse(str, ConfigDescription{});
}
- ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) {
- ResourceParserOptions parserOptions;
+ ::testing::AssertionResult TestParse(StringPiece str, ResourceParserOptions parserOptions) {
+ return TestParse(str, ConfigDescription{}, parserOptions);
+ }
+
+ ::testing::AssertionResult TestParse(
+ StringPiece str, const ConfigDescription& config,
+ ResourceParserOptions parserOptions = ResourceParserOptions()) {
ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config,
parserOptions);
@@ -242,6 +247,19 @@
EXPECT_FALSE(TestParse(R"(<string name="foo4" translatable="yes">Translate</string>)"));
}
+TEST_F(ResourceParserTest, ParseStringBehindDisabledFlag) {
+ FeatureFlagProperties flag_properties(true, false);
+ ResourceParserOptions options;
+ options.feature_flag_values = {{"falseFlag", flag_properties}};
+ ASSERT_TRUE(TestParse(
+ R"(<string name="foo" android:featureFlag="falseFlag"
+ xmlns:android="http://schemas.android.com/apk/res/android">foo</string>)",
+ options));
+
+ String* str = test::GetValue<String>(&table_, "string/foo");
+ ASSERT_THAT(str, IsNull());
+}
+
TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) {
std::string input = R"(
<string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
index 7806061..b4a7663 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
@@ -86,11 +86,12 @@
@Test
public void testAllFieldsValidV1() throws Exception {
System.out.println("starting testAllFieldsValidV1.");
- new AppInfoFactory()
- .createFromOdElement(
- TestUtils.getElementFromResource(
- Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME)),
- 1L);
+ var unused =
+ new AppInfoFactory()
+ .createFromOdElement(
+ TestUtils.getElementFromResource(
+ Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME)),
+ 1L);
}
/** Test for unrecognized field v1. */
@@ -133,7 +134,7 @@
TestUtils.getElementFromResource(
Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME));
TestUtils.removeOdChildEleWithName(appInfoEle, optField);
- new AppInfoFactory().createFromOdElement(appInfoEle, 1L);
+ var unused = new AppInfoFactory().createFromOdElement(appInfoEle, 1L);
}
}
@@ -202,7 +203,7 @@
Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
ele.removeAttribute(optField);
AppInfo appInfo = new AppInfoFactory().createFromHrElement(ele, DEFAULT_VERSION);
- appInfo.toOdDomElement(TestUtils.document());
+ var unused = appInfo.toOdDomElement(TestUtils.document());
}
for (String optField : OPTIONAL_FIELD_NAMES_OD) {
@@ -211,7 +212,7 @@
Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
TestUtils.removeOdChildEleWithName(ele, optField);
AppInfo appInfo = new AppInfoFactory().createFromOdElement(ele, DEFAULT_VERSION);
- appInfo.toHrDomElement(TestUtils.document());
+ var unused = appInfo.toHrDomElement(TestUtils.document());
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
index a4472b1..2746800 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
@@ -99,7 +99,7 @@
developerInfoEle.removeAttribute(optField);
DeveloperInfo developerInfo =
new DeveloperInfoFactory().createFromHrElement(developerInfoEle);
- developerInfo.toOdDomElement(TestUtils.document());
+ var unused = developerInfo.toOdDomElement(TestUtils.document());
}
for (String optField : OPTIONAL_FIELD_NAMES_OD) {
@@ -109,7 +109,7 @@
TestUtils.removeOdChildEleWithName(developerInfoEle, optField);
DeveloperInfo developerInfo =
new DeveloperInfoFactory().createFromOdElement(developerInfoEle);
- developerInfo.toHrDomElement(TestUtils.document());
+ var unused = developerInfo.toHrDomElement(TestUtils.document());
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
index 9d197a2..27f8720 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
@@ -64,7 +64,7 @@
Paths.get(SECURITY_LABELS_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
ele.removeAttribute(optField);
SecurityLabels securityLabels = new SecurityLabelsFactory().createFromHrElement(ele);
- securityLabels.toOdDomElement(TestUtils.document());
+ var unused = securityLabels.toOdDomElement(TestUtils.document());
}
for (String optField : OPTIONAL_FIELD_NAMES_OD) {
var ele =
@@ -72,7 +72,7 @@
Paths.get(SECURITY_LABELS_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
TestUtils.removeOdChildEleWithName(ele, optField);
SecurityLabels securityLabels = new SecurityLabelsFactory().createFromOdElement(ele);
- securityLabels.toHrDomElement(TestUtils.document());
+ var unused = securityLabels.toHrDomElement(TestUtils.document());
}
}