diff options
130 files changed, 3458 insertions, 888 deletions
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index df707d18a827..1e94c2fc219c 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -673,8 +673,8 @@ package android.webkit { method @Nullable public android.content.pm.PackageInfo getCurrentWebViewPackage(); method @Nullable public String getCurrentWebViewPackageName(); method @FlaggedApi("android.webkit.update_service_v2") @NonNull public android.webkit.WebViewProviderInfo getDefaultWebViewPackage(); - method @Nullable public static android.webkit.WebViewUpdateManager getInstance(); - method @NonNull @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.QUERY_ALL_PACKAGES}) public android.webkit.WebViewProviderInfo[] getValidWebViewPackages(); + method @NonNull public static android.webkit.WebViewUpdateManager getInstance(); + method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.webkit.WebViewProviderInfo[] getValidWebViewPackages(); method @NonNull public android.webkit.WebViewProviderResponse waitForAndGetProvider(); } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index fb425a99732a..475b1e2155ed 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -18769,7 +18769,7 @@ package android.webkit { public final class WebViewUpdateService { method public static android.webkit.WebViewProviderInfo[] getAllWebViewPackages(); method public static String getCurrentWebViewPackageName(); - method public static android.webkit.WebViewProviderInfo[] getValidWebViewPackages(); + method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public static android.webkit.WebViewProviderInfo[] getValidWebViewPackages(); } } diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 32e9542e91a7..4a2b016456f7 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -55,7 +55,9 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Objects; /** @@ -100,6 +102,11 @@ public final class NotificationChannel implements Parcelable { @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) public static final String RECS_ID = "android.app.recs"; + /** @hide */ + @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public static final ArrayList<String> SYSTEM_RESERVED_IDS = new ArrayList<>( + List.of(NEWS_ID, SOCIAL_MEDIA_ID, PROMOTIONS_ID, RECS_ID)); + /** * The formatter used by the system to create an id for notification * channels when it automatically creates conversation channels on behalf of an app. The format diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index c7b0be7553c2..46567c440111 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -1107,7 +1107,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { /** * Called to notify the state of operations that can be unsafe to execute has changed. * - * <p><b>Note:/b> notice that the operation safety state might change between the time this + * <p><b>Note:</b> notice that the operation safety state might change between the time this * callback is received and the operation's method on {@link DevicePolicyManager} is called, so * calls to the latter could still throw a {@link UnsafeStateException} even when this method * is called with {@code isSafe} as {@code true} diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index f1ec0e4e9bdc..a4a7a983c44c 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1539,10 +1539,19 @@ public class UserManager { * Specifies that the managed profile is not allowed to have unified lock screen challenge with * the primary user. * - * <p><strong>Note:</strong> Setting this restriction alone doesn't automatically set a - * separate challenge. Profile owner can ask the user to set a new password using - * {@link DevicePolicyManager#ACTION_SET_NEW_PASSWORD} and verify it using - * {@link DevicePolicyManager#isUsingUnifiedPassword(ComponentName)}. + * <p>To ensure that there is a separate work profile password, IT admins + * have to: + * <ol> + * <li>Enforce {@link UserManager#DISALLOW_UNIFIED_PASSWORD}</li> + * <li>Verify that {@link DevicePolicyManager#isUsingUnifiedPassword(ComponentName)} + * returns true. This indicates that there is now a separate work + * profile password configured and the set up is completed.</li> + * <li>In case {@link DevicePolicyManager#isUsingUnifiedPassword(ComponentName)} + * returns false, invoke {@link DevicePolicyManager#ACTION_SET_NEW_PASSWORD} + * intent and then verify again + * {@link DevicePolicyManager#isUsingUnifiedPassword(ComponentName)}.</li> + * </ol> + * </p> * * <p>Can be set by profile owners. It only has effect on managed profiles when set by managed * profile owner. Has no effect on non-managed profiles or users. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 0a05f704f523..e32625e1f7a8 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -17818,6 +17818,12 @@ public final class Settings { public static final String FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT = "force_non_debuggable_final_build_for_compat"; + /** + * Flag to enable the use of ApplicationInfo for getting not-launched status. + * + * @hide + */ + public static final String ENABLE_USE_APP_INFO_NOT_LAUNCHED = "use_app_info_not_launched"; /** * Current version of signed configuration applied. diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index f6f0eff918e3..a86c961e6785 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -99,3 +99,10 @@ flag { description: "Causes TrustManagerService to listen for credential attempts and ignore reports from upstream" bug: "323086607" } + +flag { + name: "clear_strong_auth_on_add_primary_credential" + namespace: "biometrics" + description: "Clear StrongAuth on add credential" + bug: "320817991" +} diff --git a/core/java/android/webkit/WebViewUpdateManager.java b/core/java/android/webkit/WebViewUpdateManager.java index 0eb710015ea9..b9a5e4aedeb9 100644 --- a/core/java/android/webkit/WebViewUpdateManager.java +++ b/core/java/android/webkit/WebViewUpdateManager.java @@ -43,22 +43,29 @@ public final class WebViewUpdateManager { /** * Get the singleton instance of the manager. * - * This exists for the benefit of callsites without a {@link Context}; prefer + * <p>This exists for the benefit of callsites without a {@link Context}; prefer * {@link Context#getSystemService(Class)} otherwise. * - * This can only be used on devices with {@link PackageManager#FEATURE_WEBVIEW}. + * <p>This must only be called on devices with {@link PackageManager#FEATURE_WEBVIEW}, + * and will WTF or throw {@link UnsupportedOperationException} otherwise. */ @SuppressLint("ManagerLookup") // service opts in to getSystemServiceWithNoContext() @RequiresFeature(PackageManager.FEATURE_WEBVIEW) - public static @Nullable WebViewUpdateManager getInstance() { - return (WebViewUpdateManager) SystemServiceRegistry.getSystemServiceWithNoContext( - Context.WEBVIEW_UPDATE_SERVICE); + public static @NonNull WebViewUpdateManager getInstance() { + WebViewUpdateManager manager = + (WebViewUpdateManager) SystemServiceRegistry.getSystemServiceWithNoContext( + Context.WEBVIEW_UPDATE_SERVICE); + if (manager == null) { + throw new UnsupportedOperationException("WebView not supported by device"); + } else { + return manager; + } } /** * Block until system-level WebView preparations are complete. * - * This also makes the current WebView provider package visible to the caller. + * <p>This also makes the current WebView provider package visible to the caller. * * @return the status of WebView preparation and the current provider package. */ @@ -86,7 +93,7 @@ public final class WebViewUpdateManager { /** * Get the complete list of supported WebView providers for this device. * - * This includes all configured providers, regardless of whether they are currently available + * <p>This includes all configured providers, regardless of whether they are currently available * or valid. */ @SuppressLint({"ParcelableList", "ArrayReturn"}) @@ -101,13 +108,15 @@ public final class WebViewUpdateManager { /** * Get the list of currently-valid WebView providers for this device. * - * This only includes providers that are currently present on the device and meet the validity - * criteria (signature, version, etc), but does not check if the provider is installed and - * enabled for all users. + * <p>This only includes providers that are currently present on the device and meet the + * validity criteria (signature, version, etc), but does not check if the provider is installed + * and enabled for all users. + * + * <p>Note that this will be filtered by the caller's package visibility; callers should + * have QUERY_ALL_PACKAGES permission to ensure that the list is complete. */ @SuppressLint({"ParcelableList", "ArrayReturn"}) - @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS, - android.Manifest.permission.QUERY_ALL_PACKAGES}) + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public @NonNull WebViewProviderInfo[] getValidWebViewPackages() { try { return mService.getValidWebViewPackages(); @@ -132,7 +141,7 @@ public final class WebViewUpdateManager { /** * Ask the system to switch to a specific WebView implementation if possible. * - * This choice will be stored persistently. + * <p>This choice will be stored persistently. * * @param newProvider the package name to use. * @return the package name which is now in use, which may not be the @@ -162,7 +171,7 @@ public final class WebViewUpdateManager { /** * Get the WebView provider which will be used if no explicit choice has been made. * - * The default provider is not guaranteed to be a valid/usable WebView implementation. + * <p>The default provider is not guaranteed to be a valid/usable WebView implementation. * * @return the default WebView provider. */ diff --git a/core/java/android/webkit/WebViewUpdateService.java b/core/java/android/webkit/WebViewUpdateService.java index 01af182a10fa..644d917c8be6 100644 --- a/core/java/android/webkit/WebViewUpdateService.java +++ b/core/java/android/webkit/WebViewUpdateService.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.RemoteException; @@ -54,7 +55,11 @@ public final class WebViewUpdateService { /** * Fetch all packages that could potentially implement WebView and are currently valid. + * + * <p>Note that this will be filtered by the caller's package visibility; callers should + * have QUERY_ALL_PACKAGES permission to ensure that the list is complete. */ + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public static WebViewProviderInfo[] getValidWebViewPackages() { if (Flags.updateServiceIpcWrapper()) { if (WebViewFactory.isWebViewSupported()) { diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 89ea85200cd6..e1154ca0701c 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -83,6 +83,7 @@ import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; import android.os.StrictMode; +import android.os.Trace; import android.os.UserHandle; import android.system.Os; import android.text.TextUtils; @@ -7003,6 +7004,18 @@ public class RemoteViews implements Parcelable, Filter { private View inflateView(Context context, RemoteViews rv, @Nullable ViewGroup parent, @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) { + try { + Trace.beginSection(rv.hasDrawInstructions() + ? "RemoteViews#inflateViewWithDrawInstructions" + : "RemoteViews#inflateView"); + return inflateViewInternal(context, rv, parent, applyThemeResId, colorResources); + } finally { + Trace.endSection(); + } + } + + private View inflateViewInternal(Context context, RemoteViews rv, @Nullable ViewGroup parent, + @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) { // RemoteViews may be built by an application installed in another // user. So build a context that loads resources from that user but // still returns the current users userId so settings like data / time formats @@ -7169,10 +7182,17 @@ public class RemoteViews implements Parcelable, Filter { if (mRV.mActions != null) { int count = mRV.mActions.size(); mActions = new Action[count]; - for (int i = 0; i < count && !isCancelled(); i++) { - // TODO: check if isCancelled in nested views. - mActions[i] = mRV.mActions.get(i) - .initActionAsync(mTree, mParent, mApplyParams); + try { + Trace.beginSection(hasDrawInstructions() + ? "RemoteViews#initActionAsyncWithDrawInstructions" + : "RemoteViews#initActionAsync"); + for (int i = 0; i < count && !isCancelled(); i++) { + // TODO: check if isCancelled in nested views. + mActions[i] = mRV.mActions.get(i) + .initActionAsync(mTree, mParent, mApplyParams); + } + } finally { + Trace.endSection(); } } else { mActions = null; @@ -7194,13 +7214,19 @@ public class RemoteViews implements Parcelable, Filter { try { if (mActions != null) { - ActionApplyParams applyParams = mApplyParams.clone(); if (applyParams.handler == null) { applyParams.handler = DEFAULT_INTERACTION_HANDLER; } - for (Action a : mActions) { - a.apply(viewTree.mRoot, mParent, applyParams); + try { + Trace.beginSection(hasDrawInstructions() + ? "RemoteViews#applyActionsAsyncWithDrawInstructions" + : "RemoteViews#applyActionsAsync"); + for (Action a : mActions) { + a.apply(viewTree.mRoot, mParent, applyParams); + } + } finally { + Trace.endSection(); } } // If the parent of the view is has is a root, resolve the recycling. @@ -7387,8 +7413,15 @@ public class RemoteViews implements Parcelable, Filter { } if (mActions != null) { final int count = mActions.size(); - for (int i = 0; i < count; i++) { - mActions.get(i).apply(v, parent, params); + try { + Trace.beginSection(hasDrawInstructions() + ? "RemoteViews#applyActionsWithDrawInstructions" + : "RemoteViews#applyActions"); + for (int i = 0; i < count; i++) { + mActions.get(i).apply(v, parent, params); + } + } finally { + Trace.endSection(); } } } diff --git a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java b/core/java/android/window/flags/DesktopModeFlags.java index d33313e08742..5c53d66e49fe 100644 --- a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java +++ b/core/java/android/window/flags/DesktopModeFlags.java @@ -14,9 +14,7 @@ * limitations under the License. */ -package com.android.server.wm.utils; - -import static com.android.server.wm.utils.DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET; +package android.window.flags; import android.annotation.Nullable; import android.content.Context; @@ -28,18 +26,18 @@ import com.android.window.flags.Flags; import java.util.function.Supplier; /** - * Util to check desktop mode flags state. + * Checks desktop mode flag state. * - * This utility is used to allow developer option toggles to override flags related to desktop - * windowing. + * <p>This enum provides a centralized way to control the behavior of flags related to desktop + * windowing features which are aiming for developer preview before their release. It allows + * developer option to override the default behavior of these flags. * - * Computes whether Desktop Windowing related flags should be enabled by using the aconfig flag - * value and the developer option override state (if applicable). + * <p>NOTE: Flags should only be added to this enum when they have received Product and UX + * alignment that the feature is ready for developer preview, otherwise just do a flag check. * - * This is a partial copy of {@link com.android.wm.shell.shared.desktopmode.DesktopModeFlags} which - * is to be used in WM core. + * @hide */ -public enum DesktopModeFlagsUtil { +public enum DesktopModeFlags { // All desktop mode related flags to be overridden by developer option toggle will be added here DESKTOP_WINDOWING_MODE( Flags::enableDesktopWindowingMode, /* shouldOverrideByDevOption= */ true), @@ -55,7 +53,7 @@ public enum DesktopModeFlagsUtil { // be refreshed only on reboots as overridden state is expected to take effect on reboots. private static ToggleOverride sCachedToggleOverride; - DesktopModeFlagsUtil(Supplier<Boolean> flagFunction, boolean shouldOverrideByDevOption) { + DesktopModeFlags(Supplier<Boolean> flagFunction, boolean shouldOverrideByDevOption) { this.mFlagFunction = flagFunction; this.mShouldOverrideByDevOption = shouldOverrideByDevOption; } @@ -101,13 +99,13 @@ public enum DesktopModeFlagsUtil { int settingValue = Settings.Global.getInt( context.getContentResolver(), Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, - OVERRIDE_UNSET.getSetting() + ToggleOverride.OVERRIDE_UNSET.getSetting() ); - return ToggleOverride.fromSetting(settingValue, OVERRIDE_UNSET); + return ToggleOverride.fromSetting(settingValue, ToggleOverride.OVERRIDE_UNSET); } /** Override state of desktop mode developer option toggle. */ - enum ToggleOverride { + private enum ToggleOverride { OVERRIDE_UNSET, OVERRIDE_OFF, OVERRIDE_ON; diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 8077a55bf74d..13648de5b28e 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -115,3 +115,10 @@ flag { bug: "289875940" is_fixed_read_only: true } + +flag { + namespace: "windowing_sdk" + name: "touch_pass_through_opt_in" + description: "Requires apps to opt-in to overlay pass through touches and provide APIs to opt-in" + bug: "358129114" +} diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java index 46b8e3a430c8..32345e606229 100644 --- a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java +++ b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java @@ -14,11 +14,10 @@ * limitations under the License. */ -package com.android.server.wm.utils; +package android.window.flags; + +import static android.window.flags.DesktopModeFlags.DESKTOP_WINDOWING_MODE; -import static com.android.server.wm.utils.DesktopModeFlagsUtil.DESKTOP_WINDOWING_MODE; -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_WINDOWING_DYNAMIC_INITIAL_BOUNDS; import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION; @@ -26,16 +25,16 @@ import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPT import static com.google.common.truth.Truth.assertThat; import android.content.ContentResolver; +import android.content.Context; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; - -import com.android.server.wm.WindowTestRunner; -import com.android.server.wm.WindowTestsBase; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Before; import org.junit.Rule; @@ -45,21 +44,28 @@ import org.junit.runner.RunWith; import java.lang.reflect.Field; /** - * Test class for [DesktopModeFlagsUtil] + * Test class for {@link DesktopModeFlags} * * Build/Install/Run: - * atest WmTests:DesktopModeFlagsUtilTest + * atest FrameworksCoreTests:DesktopModeFlagsTest */ @SmallTest @Presubmit -@RunWith(WindowTestRunner.class) -public class DesktopModeFlagsUtilTest extends WindowTestsBase { +@RunWith(AndroidJUnit4.class) +public class DesktopModeFlagsTest { @Rule public SetFlagsRule setFlagsRule = new SetFlagsRule(); + private Context mContext; + + private static final int OVERRIDE_OFF_SETTING = 0; + private static final int OVERRIDE_ON_SETTING = 1; + private static final int OVERRIDE_UNSET_SETTING = -1; + @Before public void setUp() throws Exception { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); resetCache(); } @@ -67,7 +73,7 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void isEnabled_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() { - setOverride(OVERRIDE_OFF.getSetting()); + setOverride(OVERRIDE_OFF_SETTING); // In absence of dev options, follow flag assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); } @@ -76,7 +82,7 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { @Test @DisableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) public void isEnabled_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() { - setOverride(OVERRIDE_ON.getSetting()); + setOverride(OVERRIDE_ON_SETTING); assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); } @@ -84,7 +90,7 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) public void isEnabled_overrideUnset_featureFlagOn_returnsTrue() { - setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()); + setOverride(OVERRIDE_UNSET_SETTING); // For overridableFlag, for unset overrides, follow flag assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); @@ -94,7 +100,7 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void isEnabled_overrideUnset_featureFlagOff_returnsFalse() { - setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()); + setOverride(OVERRIDE_UNSET_SETTING); // For overridableFlag, for unset overrides, follow flag assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); @@ -141,7 +147,7 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) public void isEnabled_overrideOff_featureFlagOn_returnsFalse() { - setOverride(OVERRIDE_OFF.getSetting()); + setOverride(OVERRIDE_OFF_SETTING); // For overridableFlag, follow override if they exist assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); @@ -151,7 +157,7 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void isEnabled_overrideOn_featureFlagOff_returnsTrue() { - setOverride(OVERRIDE_ON.getSetting()); + setOverride(OVERRIDE_ON_SETTING); // For overridableFlag, follow override if they exist assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); @@ -160,12 +166,12 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) public void isEnabled_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() { - setOverride(OVERRIDE_OFF.getSetting()); + setOverride(OVERRIDE_OFF_SETTING); // For overridableFlag, follow override if they exist assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); - setOverride(OVERRIDE_ON.getSetting()); + setOverride(OVERRIDE_ON_SETTING); // Keep overrides constant through the process assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); @@ -175,12 +181,12 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void isEnabled_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() { - setOverride(OVERRIDE_ON.getSetting()); + setOverride(OVERRIDE_ON_SETTING); // For overridableFlag, follow override if they exist assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); - setOverride(OVERRIDE_OFF.getSetting()); + setOverride(OVERRIDE_OFF_SETTING); // Keep overrides constant through the process assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); @@ -190,19 +196,19 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS}) public void isEnabled_dwFlagOn_overrideUnset_featureFlagOn_returnsTrue() { - setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()); + setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); } @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) public void isEnabled_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() { - setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()); + setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); } @Test @@ -212,20 +218,20 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS }) public void isEnabled_dwFlagOn_overrideOn_featureFlagOn_returnsTrue() { - setOverride(OVERRIDE_ON.getSetting()); + setOverride(OVERRIDE_ON_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); } @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) public void isEnabled_dwFlagOn_overrideOn_featureFlagOff_returnsFalse() { - setOverride(OVERRIDE_ON.getSetting()); + setOverride(OVERRIDE_ON_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); } @Test @@ -235,20 +241,20 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS }) public void isEnabled_dwFlagOn_overrideOff_featureFlagOn_returnsTrue() { - setOverride(OVERRIDE_OFF.getSetting()); + setOverride(OVERRIDE_OFF_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); } @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) public void isEnabled_dwFlagOn_overrideOff_featureFlagOff_returnsFalse() { - setOverride(OVERRIDE_OFF.getSetting()); + setOverride(OVERRIDE_OFF_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); } @Test @@ -258,10 +264,10 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { }) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void isEnabled_dwFlagOff_overrideUnset_featureFlagOn_returnsTrue() { - setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()); + setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); } @Test @@ -271,10 +277,10 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS }) public void isEnabled_dwFlagOff_overrideUnset_featureFlagOff_returnsFalse() { - setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()); + setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); } @Test @@ -284,10 +290,10 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { }) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void isEnabled_dwFlagOff_overrideOn_featureFlagOn_returnsTrue() { - setOverride(OVERRIDE_ON.getSetting()); + setOverride(OVERRIDE_ON_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); } @Test @@ -297,10 +303,10 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS }) public void isEnabled_dwFlagOff_overrideOn_featureFlagOff_returnFalse() { - setOverride(OVERRIDE_ON.getSetting()); + setOverride(OVERRIDE_ON_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); } @Test @@ -310,10 +316,10 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { }) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) public void isEnabled_dwFlagOff_overrideOff_featureFlagOn_returnsTrue() { - setOverride(OVERRIDE_OFF.getSetting()); + setOverride(OVERRIDE_OFF_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); + assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue(); } @Test @@ -323,10 +329,10 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS }) public void isEnabled_dwFlagOff_overrideOff_featureFlagOff_returnsFalse() { - setOverride(OVERRIDE_OFF.getSetting()); + setOverride(OVERRIDE_OFF_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); + assertThat(DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse(); } private void setOverride(Integer setting) { @@ -341,7 +347,7 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { } private void resetCache() throws Exception { - Field cachedToggleOverride = DesktopModeFlagsUtil.class.getDeclaredField( + Field cachedToggleOverride = DesktopModeFlags.class.getDeclaredField( "sCachedToggleOverride"); cachedToggleOverride.setAccessible(true); cachedToggleOverride.set(null, null); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 7054c17cfeb0..8c7dcf295319 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -18,6 +18,7 @@ package com.android.wm.shell.dagger; import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.KeyguardManager; import android.content.Context; @@ -114,6 +115,8 @@ import com.android.wm.shell.unfold.qualifier.UnfoldTransition; import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel; import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel; import com.android.wm.shell.windowdecor.WindowDecorViewModel; +import com.android.wm.shell.windowdecor.viewhost.DefaultWindowDecorViewHostSupplier; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; import dagger.Binds; import dagger.Lazy; @@ -244,7 +247,8 @@ public abstract class WMShellModule { AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, Optional<DesktopTasksLimiter> desktopTasksLimiter, - Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler) { + Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler, + WindowDecorViewHostSupplier windowDecorViewHostSupplier) { if (DesktopModeStatus.canEnterDesktopMode(context)) { return new DesktopModeWindowDecorViewModel( context, @@ -268,7 +272,8 @@ public abstract class WMShellModule { assistContentRequester, multiInstanceHelper, desktopTasksLimiter, - desktopActivityOrientationHandler); + desktopActivityOrientationHandler, + windowDecorViewHostSupplier); } return new CaptionWindowDecorViewModel( context, @@ -282,7 +287,8 @@ public abstract class WMShellModule { displayController, rootTaskDisplayAreaOrganizer, syncQueue, - transitions); + transitions, + windowDecorViewHostSupplier); } @WMSingleton @@ -371,6 +377,13 @@ public abstract class WMShellModule { context, shellInit, transitions, windowDecorViewModel); } + @WMSingleton + @Provides + static WindowDecorViewHostSupplier provideWindowDecorViewHostSupplier( + @ShellMainThread @NonNull CoroutineScope mainScope) { + return new DefaultWindowDecorViewHostSupplier(mainScope); + } + // // One handed mode // 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 1b143ebddde7..4ba84eeb2e6d 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 @@ -34,7 +34,6 @@ import static android.view.WindowManager.transitTypeToString; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; -import static com.android.wm.shell.Flags.enableFlexibleSplit; import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; @@ -1668,7 +1667,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mRootTaskInfo == null || mRootTaskInfo.taskId != taskInfo.taskId) { throw new IllegalArgumentException(this + "\n Unknown task info changed: " + taskInfo); } - mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo)); mRootTaskInfo = taskInfo; if (mSplitLayout != null && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration) @@ -2822,6 +2820,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.flingDividerToCenter(this::notifySplitAnimationFinished); } callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false); + mWindowDecorViewModel.ifPresent(viewModel -> { + viewModel.onTaskInfoChanged(finalMainChild.getTaskInfo()); + viewModel.onTaskInfoChanged(finalSideChild.getTaskInfo()); + }); mPausingTasks.clear(); }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 11976aed9315..015139519f1f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -62,6 +62,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; /** * View model for the window decoration with a caption and shadows. Works with @@ -83,6 +84,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { private final Transitions mTransitions; private final Region mExclusionRegion = Region.obtain(); private final InputManager mInputManager; + private final WindowDecorViewHostSupplier mWindowDecorViewHostSupplier; private TaskOperations mTaskOperations; /** @@ -120,7 +122,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { DisplayController displayController, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, SyncTransactionQueue syncQueue, - Transitions transitions) { + Transitions transitions, + WindowDecorViewHostSupplier windowDecorViewHostSupplier) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; @@ -132,6 +135,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mSyncQueue = syncQueue; mTransitions = transitions; + mWindowDecorViewHostSupplier = windowDecorViewHostSupplier; if (!Transitions.ENABLE_SHELL_TRANSITIONS) { mTaskOperations = new TaskOperations(null, mContext, mSyncQueue); } @@ -295,7 +299,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mMainHandler, mBgExecutor, mMainChoreographer, - mSyncQueue); + mSyncQueue, + mWindowDecorViewHostSupplier); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); final FluidResizeTaskPositioner taskPositioner = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 349ee0b1fcda..46fe68f44bed 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -56,6 +56,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.windowdecor.extension.TaskInfoKt; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; /** * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with @@ -88,8 +89,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL Handler handler, @ShellBackgroundThread ShellExecutor bgExecutor, Choreographer choreographer, - SyncTransactionQueue syncQueue) { - super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface); + SyncTransactionQueue syncQueue, + WindowDecorViewHostSupplier windowDecorViewHostSupplier) { + super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface, + windowDecorViewHostSupplier); mHandler = handler; mBgExecutor = bgExecutor; mChoreographer = choreographer; 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 0f8bd2866fa8..7692bd7b57e4 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 @@ -41,6 +41,7 @@ import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.Indica import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import android.annotation.NonNull; @@ -122,6 +123,7 @@ import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionReg import com.android.wm.shell.windowdecor.extension.InsetsStateKt; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; import kotlin.Unit; @@ -155,6 +157,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final InteractionJankMonitor mInteractionJankMonitor; private final MultiInstanceHelper mMultiInstanceHelper; private final Optional<DesktopTasksLimiter> mDesktopTasksLimiter; + private final WindowDecorViewHostSupplier mWindowDecorViewHostSupplier; private boolean mTransitionDragActive; private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>(); @@ -223,8 +226,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, Optional<DesktopTasksLimiter> desktopTasksLimiter, - Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler - ) { + Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler, + WindowDecorViewHostSupplier windowDecorViewHostSupplier) { this( context, shellExecutor, @@ -244,6 +247,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { genericLinksParser, assistContentRequester, multiInstanceHelper, + windowDecorViewHostSupplier, new DesktopModeWindowDecoration.Factory(), new InputMonitorFactory(), SurfaceControl.Transaction::new, @@ -275,6 +279,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, + WindowDecorViewHostSupplier windowDecorViewHostSupplier, DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory, InputMonitorFactory inputMonitorFactory, Supplier<SurfaceControl.Transaction> transactionFactory, @@ -300,6 +305,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mMultiInstanceHelper = multiInstanceHelper; mShellCommandHandler = shellCommandHandler; mWindowManager = windowManager; + mWindowDecorViewHostSupplier = windowDecorViewHostSupplier; mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory; mInputMonitorFactory = inputMonitorFactory; mTransactionFactory = transactionFactory; @@ -1098,8 +1104,22 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { // If we are entering split select, handle will no longer be visible and // should not be receiving any input. if (resultType == TO_SPLIT_LEFT_INDICATOR - || resultType != TO_SPLIT_RIGHT_INDICATOR) { + || resultType == TO_SPLIT_RIGHT_INDICATOR) { relevantDecor.disposeStatusBarInputLayer(); + // We should also dispose the other split task's input layer if + // applicable. + final int splitPosition = mSplitScreenController + .getSplitPosition(relevantDecor.mTaskInfo.taskId); + if (splitPosition != SPLIT_POSITION_UNDEFINED) { + final int oppositePosition = + splitPosition == SPLIT_POSITION_TOP_OR_LEFT + ? SPLIT_POSITION_BOTTOM_OR_RIGHT + : SPLIT_POSITION_TOP_OR_LEFT; + final RunningTaskInfo oppositeTaskInfo = + mSplitScreenController.getTaskInfo(oppositePosition); + mWindowDecorByTaskId.get(oppositeTaskInfo.taskId) + .disposeStatusBarInputLayer(); + } } mMoveToDesktopAnimator = null; return; @@ -1284,7 +1304,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mRootTaskDisplayAreaOrganizer, mGenericLinksParser, mAssistContentRequester, - mMultiInstanceHelper); + mMultiInstanceHelper, + mWindowDecorViewHostSupplier); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); final TaskPositioner taskPositioner = mTaskPositionerFactory.create( 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 142be91fe942..1409d3011395 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 @@ -35,7 +35,6 @@ import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResiz import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.WindowConfiguration.WindowingMode; import android.app.assist.AssistContent; @@ -92,6 +91,7 @@ import com.android.wm.shell.windowdecor.extension.TaskInfoKt; import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder; import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; import kotlin.Unit; import kotlin.jvm.functions.Function0; @@ -134,12 +134,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private DragPositioningCallback mDragPositioningCallback; private DragResizeInputListener mDragResizeListener; private DragDetector mDragDetector; - private Runnable mCurrentViewHostRunnable = null; private RelayoutParams mRelayoutParams = new RelayoutParams(); private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult = new WindowDecoration.RelayoutResult<>(); - private final Runnable mViewHostRunnable = - () -> updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mResult); private final Point mPositionInParent = new Point(); private HandleMenu mHandleMenu; @@ -190,14 +187,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, - MultiInstanceHelper multiInstanceHelper) { + MultiInstanceHelper multiInstanceHelper, + WindowDecorViewHostSupplier windowDecorViewHostSupplier) { this (context, userContext, displayController, splitScreenController, taskOrganizer, taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue, rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester, SurfaceControl.Builder::new, SurfaceControl.Transaction::new, WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper( context.getSystemService(WindowManager.class)), - new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE, + new SurfaceControlViewHostFactory() {}, windowDecorViewHostSupplier, + DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper); } @@ -222,13 +221,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Supplier<SurfaceControl> surfaceControlSupplier, WindowManagerWrapper windowManagerWrapper, SurfaceControlViewHostFactory surfaceControlViewHostFactory, + WindowDecorViewHostSupplier windowDecorViewHostSupplier, MaximizeMenuFactory maximizeMenuFactory, HandleMenuFactory handleMenuFactory, MultiInstanceHelper multiInstanceHelper) { super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, windowContainerTransactionSupplier, surfaceControlSupplier, - surfaceControlViewHostFactory); + surfaceControlViewHostFactory, windowDecorViewHostSupplier); mSplitScreenController = splitScreenController; mHandler = handler; mBgExecutor = bgExecutor; @@ -337,73 +337,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { Trace.beginSection("DesktopModeWindowDecoration#relayout"); - if (taskInfo.isFreeform()) { - // The Task is in Freeform mode -> show its header in sync since it's an integral part - // of the window itself - a delayed header might cause bad UX. - relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskPositionAndCrop); - } else { - // The Task is outside Freeform mode -> allow the handle view to be delayed since the - // handle is just a small addition to the window. - relayoutWithDelayedViewHost(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskPositionAndCrop); - } - Trace.endSection(); - } - - /** Run the whole relayout phase immediately without delay. */ - private void relayoutInSync(ActivityManager.RunningTaskInfo taskInfo, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { - // Clear the current ViewHost runnable as we will update the ViewHost here - clearCurrentViewHostRunnable(); - updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskPositionAndCrop); - if (mResult.mRootView != null) { - updateViewHost(mRelayoutParams, startT, mResult); - } - } - - /** - * Clear the current ViewHost runnable - to ensure it doesn't run once relayout params have been - * updated. - */ - private void clearCurrentViewHostRunnable() { - if (mCurrentViewHostRunnable != null) { - mHandler.removeCallbacks(mCurrentViewHostRunnable); - mCurrentViewHostRunnable = null; - } - } - - /** - * Relayout the window decoration but repost some of the work, to unblock the current callstack. - */ - private void relayoutWithDelayedViewHost(ActivityManager.RunningTaskInfo taskInfo, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { - if (applyStartTransactionOnDraw) { - throw new IllegalArgumentException( - "We cannot both sync viewhost ondraw and delay viewhost creation."); - } - // Clear the current ViewHost runnable as we will update the ViewHost here - clearCurrentViewHostRunnable(); - updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, - false /* applyStartTransactionOnDraw */, shouldSetTaskPositionAndCrop); - if (mResult.mRootView == null) { - // This means something blocks the window decor from showing, e.g. the task is hidden. - // Nothing is set up in this case including the decoration surface. - return; - } - // Store the current runnable so it can be removed if we start a new relayout. - mCurrentViewHostRunnable = mViewHostRunnable; - mHandler.post(mCurrentViewHostRunnable); - } - - @SuppressLint("MissingPermission") - private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { - Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces"); if (Flags.enableDesktopWindowingAppToWeb()) { setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp); @@ -420,8 +353,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); - Trace.beginSection("DesktopModeWindowDecoration#relayout-updateViewsAndSurfaces"); - updateViewsAndSurfaces(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); + Trace.beginSection("DesktopModeWindowDecoration#relayout-inner"); + relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); Trace.endSection(); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo @@ -433,7 +366,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // This means something blocks the window decor from showing, e.g. the task is hidden. // Nothing is set up in this case including the decoration surface. disposeStatusBarInputLayer(); - Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces + Trace.endSection(); // DesktopModeWindowDecoration#relayout return; } @@ -441,12 +374,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin disposeStatusBarInputLayer(); mWindowDecorViewHolder = createViewHolder(); } - Trace.beginSection("DesktopModeWindowDecoration#relayout-binding"); final Point position = new Point(); if (isAppHandle(mWindowDecorViewHolder)) { position.set(determineHandlePosition()); } + Trace.beginSection("DesktopModeWindowDecoration#relayout-bindData"); mWindowDecorViewHolder.bindData(mTaskInfo, position, mResult.mCaptionWidth, @@ -460,7 +393,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } updateDragResizeListener(oldDecorationSurface); updateMaximizeMenu(startT); - Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces + Trace.endSection(); // DesktopModeWindowDecoration#relayout } private boolean isCaptionVisible() { @@ -638,6 +571,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin relayoutParams.mLayoutResId = captionLayoutId; relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode()); relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId); + // Allow the handle view to be delayed since the handle is just a small addition to the + // window, whereas the header cannot be delayed because it is expected to be visible from + // the first frame. + relayoutParams.mAsyncViewHost = isAppHandle; if (isAppHeader) { if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { @@ -1253,7 +1190,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId); disposeResizeVeil(); disposeStatusBarInputLayer(); - clearCurrentViewHostRunnable(); super.close(); } @@ -1364,7 +1300,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, - MultiInstanceHelper multiInstanceHelper) { + MultiInstanceHelper multiInstanceHelper, + WindowDecorViewHostSupplier windowDecorViewHostSupplier) { return new DesktopModeWindowDecoration( context, userContext, @@ -1380,7 +1317,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester, - multiInstanceHelper); + multiInstanceHelper, + windowDecorViewHostSupplier); } } 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 c1a55b48a02a..369484558325 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 @@ -62,6 +62,8 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement; import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer; import com.android.wm.shell.windowdecor.extension.InsetsStateKt; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHost; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; import java.util.ArrayList; import java.util.Arrays; @@ -116,6 +118,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier; final Supplier<WindowContainerTransaction> mWindowContainerTransactionSupplier; final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory; + @NonNull private final WindowDecorViewHostSupplier mWindowDecorViewHostSupplier; private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener = new DisplayController.OnDisplaysChangedListener() { @Override @@ -137,9 +140,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> Context mDecorWindowContext; SurfaceControl mDecorationContainerSurface; - SurfaceControl mCaptionContainerSurface; - private WindowlessWindowManager mCaptionWindowManager; - private SurfaceControlViewHost mViewHost; + private WindowDecorViewHost mDecorViewHost; private Configuration mWindowDecorConfig; TaskDragResizer mTaskDragResizer; boolean mIsCaptionVisible; @@ -158,11 +159,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> DisplayController displayController, ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, - SurfaceControl taskSurface) { + SurfaceControl taskSurface, + @NonNull WindowDecorViewHostSupplier windowDecorViewHostSupplier) { this(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface, SurfaceControl.Builder::new, SurfaceControl.Transaction::new, WindowContainerTransaction::new, SurfaceControl::new, - new SurfaceControlViewHostFactory() {}); + new SurfaceControlViewHostFactory() {}, + windowDecorViewHostSupplier); } WindowDecoration( @@ -176,7 +179,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, Supplier<SurfaceControl> surfaceControlSupplier, - SurfaceControlViewHostFactory surfaceControlViewHostFactory) { + SurfaceControlViewHostFactory surfaceControlViewHostFactory, + @NonNull WindowDecorViewHostSupplier windowDecorViewHostSupplier) { mContext = context; mUserContext = userContext; mDisplayController = displayController; @@ -187,6 +191,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier; mWindowContainerTransactionSupplier = windowContainerTransactionSupplier; mSurfaceControlViewHostFactory = surfaceControlViewHostFactory; + mWindowDecorViewHostSupplier = windowDecorViewHostSupplier; mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); final InsetsState insetsState = mDisplayController.getInsetsState(mTaskInfo.displayId); mIsStatusBarVisible = insetsState != null @@ -212,15 +217,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> void relayout(RelayoutParams params, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult) { - updateViewsAndSurfaces(params, startT, finishT, wct, rootView, outResult); - if (outResult.mRootView != null) { - updateViewHost(params, startT, outResult); - } - } - - protected void updateViewsAndSurfaces(RelayoutParams params, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult) { + Trace.beginSection("WindowDecoration#relayout"); outResult.reset(); if (params.mRunningTaskInfo != null) { mTaskInfo = params.mRunningTaskInfo; @@ -231,17 +228,21 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> if (!mTaskInfo.isVisible) { releaseViews(wct); finishT.hide(mTaskSurface); + Trace.endSection(); // WindowDecoration#relayout return; } - + Trace.beginSection("WindowDecoration#relayout-inflateIfNeeded"); inflateIfNeeded(params, wct, rootView, oldLayoutResId, outResult); - if (outResult.mRootView == null) { - // Didn't manage to create a root view, early out. + Trace.endSection(); + final boolean hasCaptionView = outResult.mRootView != null; + if (!hasCaptionView) { + Trace.endSection(); // WindowDecoration#relayout return; } - rootView = null; // Clear it just in case we use it accidentally + Trace.beginSection("WindowDecoration#relayout-updateCaptionVisibility"); updateCaptionVisibility(outResult.mRootView); + Trace.endSection(); final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds(); outResult.mWidth = taskBounds.width(); @@ -254,10 +255,23 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> ? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width(); outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2; + Trace.beginSection("WindowDecoration#relayout-acquire"); + if (mDecorViewHost == null) { + mDecorViewHost = mWindowDecorViewHostSupplier.acquire(mDecorWindowContext, mDisplay); + } + Trace.endSection(); + + final SurfaceControl captionSurface = mDecorViewHost.getSurfaceControl(); + Trace.beginSection("WindowDecoration#relayout-updateSurfacesAndInsets"); updateDecorationContainerSurface(startT, outResult); - updateCaptionContainerSurface(startT, outResult); + updateCaptionContainerSurface(captionSurface, startT, outResult); updateCaptionInsets(params, wct, outResult, taskBounds); updateTaskSurface(params, startT, finishT, outResult); + Trace.endSection(); + + outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0); + updateViewHierarchy(params, outResult, startT); + Trace.endSection(); // WindowDecoration#relayout } private void inflateIfNeeded(RelayoutParams params, WindowContainerTransaction wct, @@ -305,6 +319,32 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> return (T) LayoutInflater.from(context).inflate(layoutResId, null); } + private void updateViewHierarchy(@NonNull RelayoutParams params, + @NonNull RelayoutResult<T> outResult, @NonNull SurfaceControl.Transaction startT) { + Trace.beginSection("WindowDecoration#updateViewHierarchy"); + final WindowManager.LayoutParams lp = + new WindowManager.LayoutParams( + outResult.mCaptionWidth, + outResult.mCaptionHeight, + TYPE_APPLICATION, + FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH, + PixelFormat.TRANSPARENT); + lp.setTitle("Caption of Task=" + mTaskInfo.taskId); + lp.setTrustedOverlay(); + lp.inputFeatures = params.mInputFeatures; + if (params.mAsyncViewHost) { + if (params.mApplyStartTransactionOnDraw) { + throw new IllegalArgumentException( + "We cannot both sync viewhost ondraw and delay viewhost creation."); + } + mDecorViewHost.updateViewAsync(outResult.mRootView, lp, mTaskInfo.getConfiguration()); + } else { + mDecorViewHost.updateView(outResult.mRootView, lp, mTaskInfo.getConfiguration(), + params.mApplyStartTransactionOnDraw ? startT : null); + } + Trace.endSection(); + } + private void updateDecorationContainerSurface( SurfaceControl.Transaction startT, RelayoutResult<T> outResult) { if (mDecorationContainerSurface == null) { @@ -325,23 +365,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .show(mDecorationContainerSurface); } - private void updateCaptionContainerSurface( + private void updateCaptionContainerSurface(@NonNull SurfaceControl captionSurface, SurfaceControl.Transaction startT, RelayoutResult<T> outResult) { - if (mCaptionContainerSurface == null) { - final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); - mCaptionContainerSurface = builder - .setName("Caption container of Task=" + mTaskInfo.taskId) - .setContainerLayer() - .setParent(mDecorationContainerSurface) - .setCallsite("WindowDecoration.updateCaptionContainerSurface") - .build(); - } - - startT.setWindowCrop(mCaptionContainerSurface, outResult.mCaptionWidth, + startT.reparent(captionSurface, mDecorationContainerSurface) + .setWindowCrop(captionSurface, outResult.mCaptionWidth, outResult.mCaptionHeight) - .setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */) - .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER) - .show(mCaptionContainerSurface); + .setPosition(captionSurface, outResult.mCaptionX, 0 /* y */) + .setLayer(captionSurface, CAPTION_LAYER_Z_ORDER) + .show(captionSurface); } private void updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct, @@ -435,64 +466,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } } - /** - * Updates a {@link SurfaceControlViewHost} to connect the window decoration surfaces with our - * View hierarchy. - * - * @param params parameters to use from the last relayout - * @param onDrawTransaction a transaction to apply in sync with #onDraw - * @param outResult results to use from the last relayout - * - */ - protected void updateViewHost(RelayoutParams params, - SurfaceControl.Transaction onDrawTransaction, RelayoutResult<T> outResult) { - Trace.beginSection("CaptionViewHostLayout"); - if (mCaptionWindowManager == null) { - // Put caption under a container surface because ViewRootImpl sets the destination frame - // of windowless window layers and BLASTBufferQueue#update() doesn't support offset. - mCaptionWindowManager = new WindowlessWindowManager( - mTaskInfo.getConfiguration(), mCaptionContainerSurface, - null /* hostInputToken */); - } - mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration()); - final WindowManager.LayoutParams lp = - new WindowManager.LayoutParams( - outResult.mCaptionWidth, - outResult.mCaptionHeight, - TYPE_APPLICATION, - FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH, - PixelFormat.TRANSPARENT); - lp.setTitle("Caption of Task=" + mTaskInfo.taskId); - lp.setTrustedOverlay(); - lp.inputFeatures = params.mInputFeatures; - if (mViewHost == null) { - Trace.beginSection("CaptionViewHostLayout-new"); - mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay, - mCaptionWindowManager); - if (params.mApplyStartTransactionOnDraw) { - if (onDrawTransaction == null) { - throw new IllegalArgumentException("Trying to sync a null Transaction"); - } - mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction); - } - outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0); - mViewHost.setView(outResult.mRootView, lp); - Trace.endSection(); - } else { - Trace.beginSection("CaptionViewHostLayout-relayout"); - if (params.mApplyStartTransactionOnDraw) { - if (onDrawTransaction == null) { - throw new IllegalArgumentException("Trying to sync a null Transaction"); - } - mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction); - } - outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0); - mViewHost.relayout(lp); - Trace.endSection(); - } - Trace.endSection(); // CaptionViewHostLayout - } - private Rect calculateBoundingRect(@NonNull OccludingCaptionElement element, int elementWidthPx, @NonNull Rect captionRect) { switch (element.mAlignment) { @@ -580,18 +553,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } void releaseViews(WindowContainerTransaction wct) { - if (mViewHost != null) { - mViewHost.release(); - mViewHost = null; - } - - mCaptionWindowManager = null; - final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); boolean released = false; - if (mCaptionContainerSurface != null) { - t.remove(mCaptionContainerSurface); - mCaptionContainerSurface = null; + if (mDecorViewHost != null) { + mWindowDecorViewHostSupplier.release(mDecorViewHost, t); + mDecorViewHost = null; released = true; } @@ -742,6 +708,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> boolean mApplyStartTransactionOnDraw; boolean mSetTaskPositionAndCrop; + boolean mAsyncViewHost; void reset() { mLayoutResId = Resources.ID_NULL; @@ -758,6 +725,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mApplyStartTransactionOnDraw = false; mSetTaskPositionAndCrop = false; + mAsyncViewHost = false; mWindowDecorConfig = null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt new file mode 100644 index 000000000000..139e6790b744 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt @@ -0,0 +1,146 @@ +/* + * 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.windowdecor.viewhost + +import android.content.Context +import android.content.res.Configuration +import android.view.Display +import android.view.SurfaceControl +import android.view.SurfaceControlViewHost +import android.view.View +import android.view.WindowManager +import android.view.WindowlessWindowManager +import androidx.tracing.Trace +import com.android.internal.annotations.VisibleForTesting +import com.android.wm.shell.shared.annotations.ShellMainThread +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +typealias SurfaceControlViewHostFactory = + (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost + +/** + * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHost]. + * + * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and + * any attempts to do will throw, which means that once a [View] is added using [updateView] or + * [updateViewAsync], only its properties and binding may be changed, its children views may be + * added, removed or changed and its [WindowManager.LayoutParams] may be changed. + * It also supports asynchronously updating the view hierarchy using [updateViewAsync], in which + * case the update work will be posted on the [ShellMainThread] with no delay. + */ +class DefaultWindowDecorViewHost( + private val context: Context, + @ShellMainThread private val mainScope: CoroutineScope, + private val display: Display, + private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s -> + SurfaceControlViewHost(c, d, wwm, s) + } +) : WindowDecorViewHost { + + private val rootSurface: SurfaceControl = SurfaceControl.Builder() + .setName("DefaultWindowDecorViewHost surface") + .setContainerLayer() + .setCallsite("DefaultWindowDecorViewHost#init") + .build() + + private var wwm: WindowlessWindowManager? = null + @VisibleForTesting + var viewHost: SurfaceControlViewHost? = null + private var currentUpdateJob: Job? = null + + override val surfaceControl: SurfaceControl + get() = rootSurface + + override fun updateView( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + onDrawTransaction: SurfaceControl.Transaction? + ) { + Trace.beginSection("DefaultWindowDecorViewHost#updateView") + clearCurrentUpdateJob() + updateViewHost(view, attrs, configuration, onDrawTransaction) + Trace.endSection() + } + + override fun updateViewAsync( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration + ) { + Trace.beginSection("DefaultWindowDecorViewHost#updateViewAsync") + clearCurrentUpdateJob() + currentUpdateJob = mainScope.launch { + updateViewHost(view, attrs, configuration, onDrawTransaction = null) + } + Trace.endSection() + } + + override fun release(t: SurfaceControl.Transaction) { + clearCurrentUpdateJob() + viewHost?.release() + t.remove(rootSurface) + } + + private fun updateViewHost( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + onDrawTransaction: SurfaceControl.Transaction? + ) { + Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost") + if (wwm == null) { + wwm = WindowlessWindowManager(configuration, rootSurface, null) + } + requireWindowlessWindowManager().setConfiguration(configuration) + if (viewHost == null) { + viewHost = surfaceControlViewHostFactory.invoke( + context, + display, + requireWindowlessWindowManager(), + "DefaultWindowDecorViewHost#updateViewHost" + ) + } + onDrawTransaction?.let { + requireViewHost().rootSurfaceControl.applyTransactionOnDraw(it) + } + if (requireViewHost().view == null) { + Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-setView") + requireViewHost().setView(view, attrs) + Trace.endSection() + } else { + check(requireViewHost().view == view) { "Changing view is not allowed" } + Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-relayout") + requireViewHost().relayout(attrs) + Trace.endSection() + } + Trace.endSection() + } + + private fun clearCurrentUpdateJob() { + currentUpdateJob?.cancel() + currentUpdateJob = null + } + + private fun requireWindowlessWindowManager(): WindowlessWindowManager { + return wwm ?: error("Expected non-null windowless window manager") + } + + private fun requireViewHost(): SurfaceControlViewHost { + return viewHost ?: error("Expected non-null view host") + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostSupplier.kt new file mode 100644 index 000000000000..9997e8f564d8 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostSupplier.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.wm.shell.windowdecor.viewhost + +import android.content.Context +import android.view.Display +import android.view.SurfaceControl +import com.android.wm.shell.shared.annotations.ShellMainThread +import kotlinx.coroutines.CoroutineScope + +/** + * A supplier of [DefaultWindowDecorViewHost]s. It creates a new one every time one is requested. + */ +class DefaultWindowDecorViewHostSupplier( + @ShellMainThread private val mainScope: CoroutineScope, +) : WindowDecorViewHostSupplier<DefaultWindowDecorViewHost> { + + override fun acquire(context: Context, display: Display): DefaultWindowDecorViewHost { + return DefaultWindowDecorViewHost(context, mainScope, display) + } + + override fun release(viewHost: DefaultWindowDecorViewHost, t: SurfaceControl.Transaction) { + viewHost.release(t) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHost.kt new file mode 100644 index 000000000000..3fbaea8bd1bf --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHost.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.viewhost + +import android.content.res.Configuration +import android.view.SurfaceControl +import android.view.View +import android.view.WindowManager +import com.android.wm.shell.windowdecor.WindowDecoration + +/** + * An interface for a utility that hosts a [WindowDecoration]'s [View] hierarchy under a + * [SurfaceControl]. + */ +interface WindowDecorViewHost { + /** The surface where the underlying [View] hierarchy is being rendered. */ + val surfaceControl: SurfaceControl + + /** Synchronously update the view hierarchy of this view host. */ + fun updateView( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + onDrawTransaction: SurfaceControl.Transaction? + ) + + /** Asynchronously update the view hierarchy of this view host. */ + fun updateViewAsync( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration + ) + + /** Releases the underlying [View] hierarchy and removes the backing [SurfaceControl]. */ + fun release(t: SurfaceControl.Transaction) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHostSupplier.kt new file mode 100644 index 000000000000..0e2358446d12 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHostSupplier.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.wm.shell.windowdecor.viewhost + +import android.content.Context +import android.view.Display +import android.view.SurfaceControl + +/** + * An interface for a supplier of [WindowDecorViewHost]s. + */ +interface WindowDecorViewHostSupplier<T : WindowDecorViewHost> { + /** Acquire a [WindowDecorViewHost]. */ + fun acquire(context: Context, display: Display): T + + /** + * Release a [WindowDecorViewHost] when it is no longer used. + * + * @param viewHost the [WindowDecorViewHost] to release + * @param t a transaction that may be used to remove any underlying backing [SurfaceControl] + * that are hosting this [WindowDecorViewHost]. The supplier is not expected to apply + * the transaction. It should be applied by the owner of this supplier. + */ + fun release(viewHost: T, t: SurfaceControl.Transaction) +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 2c805e8e77b3..a17d08d8fbfb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -98,6 +98,7 @@ import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier import java.util.Optional import java.util.function.Consumer import java.util.function.Supplier @@ -181,6 +182,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockTaskPositionerFactory: DesktopModeWindowDecorViewModel.TaskPositionerFactory @Mock private lateinit var mockTaskPositioner: TaskPositioner + @Mock private lateinit var mockWindowDecorViewHostSupplier: WindowDecorViewHostSupplier<*> private lateinit var spyContext: TestableContext private val transactionFactory = Supplier<SurfaceControl.Transaction> { @@ -230,6 +232,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockGenericLinksParser, mockAssistContentRequester, mockMultiInstanceHelper, + mockWindowDecorViewHostSupplier, mockDesktopModeWindowDecorFactory, mockInputMonitorFactory, transactionFactory, @@ -1197,7 +1200,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever( mockDesktopModeWindowDecorFactory.create( any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), - any(), any(), any(), any()) + any(), any(), any(), any(), any()) ).thenReturn(decoration) decoration.mTaskInfo = task whenever(decoration.isFocused).thenReturn(task.isFocused) 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 b9e542a0e0c1..7b68ddf64fce 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 @@ -97,6 +97,8 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHost; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; import kotlin.Unit; import kotlin.jvm.functions.Function0; @@ -162,6 +164,10 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory; @Mock + private WindowDecorViewHostSupplier mMockWindowDecorViewHostSupplier; + @Mock + private WindowDecorViewHost mMockWindowDecorViewHost; + @Mock private TypedArray mMockRoundedCornersRadiusArray; @Mock private TestTouchEventListener mMockTouchEventListener; @@ -233,6 +239,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { any(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt())) .thenReturn(mMockHandleMenu); when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false); + when(mMockWindowDecorViewHostSupplier.acquire(any(), eq(defaultDisplay))) + .thenReturn(mMockWindowDecorViewHost); + when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class)); } @After @@ -504,58 +513,55 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - public void relayout_fullscreenTask_appliesTransactionImmediately() { + public void updateRelayoutParams_handle_requestsAsyncViewHostRendering() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); + // Make the task fullscreen so that its decoration is an App Handle. taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final RelayoutParams relayoutParams = new RelayoutParams(); - spyWindowDecor.relayout(taskInfo); + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false); - verify(mMockTransaction).apply(); - verify(mMockRootSurfaceControl, never()).applyTransactionOnDraw(any()); + // App Handles don't need to be rendered in sync with the task animation, per UX. + assertThat(relayoutParams.mAsyncViewHost).isTrue(); } @Test - public void relayout_freeformTask_appliesTransactionOnDraw() { + public void updateRelayoutParams_header_requestsSyncViewHostRendering() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); + // Make the task freeform so that its decoration is an App Header. taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - // Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT) - taskInfo.isResizeable = false; - - spyWindowDecor.relayout(taskInfo); - - verify(mMockTransaction, never()).apply(); - verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockTransaction); - } - - @Test - public void relayout_fullscreenTask_doesNotCreateViewHostImmediately() { - final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); - taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final RelayoutParams relayoutParams = new RelayoutParams(); - spyWindowDecor.relayout(taskInfo); + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false); - verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any()); + // App Headers must be rendered in sync with the task animation, so it cannot be delayed. + assertThat(relayoutParams.mAsyncViewHost).isFalse(); } @Test - public void relayout_fullscreenTask_postsViewHostCreation() { + public void relayout_fullscreenTask_appliesTransactionImmediately() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); spyWindowDecor.relayout(taskInfo); - verify(mMockHandler).post(runnableArgument.capture()); - runnableArgument.getValue().run(); - verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any()); + verify(mMockTransaction).apply(); + verify(mMockRootSurfaceControl, never()).applyTransactionOnDraw(any()); } @Test - public void relayout_freeformTask_createsViewHostImmediately() { + public void relayout_freeformTask_appliesTransactionOnDraw() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); @@ -564,36 +570,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { spyWindowDecor.relayout(taskInfo); - verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any()); - verify(mMockHandler, never()).post(any()); - } - - @Test - public void relayout_removesExistingHandlerCallback() { - final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); - taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); - spyWindowDecor.relayout(taskInfo); - verify(mMockHandler).post(runnableArgument.capture()); - - spyWindowDecor.relayout(taskInfo); - - verify(mMockHandler).removeCallbacks(runnableArgument.getValue()); - } - - @Test - public void close_removesExistingHandlerCallback() { - final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); - taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); - spyWindowDecor.relayout(taskInfo); - verify(mMockHandler).post(runnableArgument.capture()); - - spyWindowDecor.close(); - - verify(mMockHandler).removeCallbacks(runnableArgument.getValue()); + verify(mMockTransaction, never()).apply(); + verify(mMockWindowDecorViewHost).updateView(any(), any(), any(), eq(mMockTransaction)); } @Test @@ -924,7 +902,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new, mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory, - maximizeMenuFactory, mMockHandleMenuFactory, mMockMultiInstanceHelper); + mMockWindowDecorViewHostSupplier, maximizeMenuFactory, mMockHandleMenuFactory, + mMockMultiInstanceHelper); windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener); windowDecor.setExclusionRegionListener(mMockExclusionRegionListener); 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 2e117ac9f865..7252b32efc6b 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 @@ -85,6 +85,8 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.tests.R; import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHost; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; import org.junit.Before; import org.junit.Rule; @@ -128,6 +130,10 @@ public class WindowDecorationTests extends ShellTestCase { @Mock private SurfaceControlViewHost mMockSurfaceControlViewHost; @Mock + private WindowDecorViewHostSupplier mMockWindowDecorViewHostSupplier; + @Mock + private WindowDecorViewHost mMockWindowDecorViewHost; + @Mock private AttachedSurfaceControl mMockRootSurfaceControl; @Mock private TestView mMockView; @@ -167,6 +173,9 @@ public class WindowDecorationTests extends ShellTestCase { when(mMockSurfaceControlViewHost.getRootSurfaceControl()) .thenReturn(mMockRootSurfaceControl); when(mMockView.findViewById(anyInt())).thenReturn(mMockView); + when(mMockWindowDecorViewHostSupplier.acquire(any(), any())) + .thenReturn(mMockWindowDecorViewHost); + when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class)); // Add status bar inset so that WindowDecoration does not think task is in immersive mode mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true); @@ -230,10 +239,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() .setDisplayId(Display.DEFAULT_DISPLAY) @@ -254,18 +259,18 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true); verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100); - verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); - verify(captionContainerSurfaceBuilder).setContainerLayer(); + final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl(); + verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface); verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); - verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any()); - - verify(mMockSurfaceControlViewHost) - .setView(same(mMockView), - argThat(lp -> lp.height == 64 - && lp.width == 300 - && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0)); + verify(mMockWindowDecorViewHost).updateView( + same(mMockView), + argThat(lp -> lp.height == 64 + && lp.width == 300 + && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0), + eq(taskInfo.configuration), + eq(null) /* onDrawTransaction */); verify(mMockView).setTaskFocusState(true); verify(mMockWindowContainerTransaction).addInsetsSource( eq(taskInfo.token), @@ -296,10 +301,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mMockSurfaceControlTransactions.add(t); @@ -322,7 +323,7 @@ public class WindowDecorationTests extends ShellTestCase { windowDecor.relayout(taskInfo); - verify(mMockSurfaceControlViewHost, never()).release(); + verify(mMockWindowDecorViewHost, never()).release(any()); verify(t, never()).apply(); verify(mMockWindowContainerTransaction, never()) .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt()); @@ -332,9 +333,8 @@ public class WindowDecorationTests extends ShellTestCase { taskInfo.isVisible = false; windowDecor.relayout(taskInfo); - final InOrder releaseOrder = inOrder(t2, mMockSurfaceControlViewHost); - releaseOrder.verify(mMockSurfaceControlViewHost).release(); - releaseOrder.verify(t2).remove(captionContainerSurface); + final InOrder releaseOrder = inOrder(t2, mMockWindowDecorViewHostSupplier); + releaseOrder.verify(mMockWindowDecorViewHostSupplier).release(mMockWindowDecorViewHost, t2); releaseOrder.verify(t2).remove(decorContainerSurface); releaseOrder.verify(t2).apply(); // Expect to remove two insets sources, the caption insets and the mandatory gesture insets. @@ -382,8 +382,8 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockDisplayController).removeDisplayWindowListener(same(listener)); assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView); - verify(mMockSurfaceControlViewHostFactory).create(any(), eq(mockDisplay), any()); - verify(mMockSurfaceControlViewHost).setView(same(mMockView), any()); + verify(mMockWindowDecorViewHostSupplier).acquire(any(), eq(mockDisplay)); + verify(mMockWindowDecorViewHost).updateView(same(mMockView), any(), any(), any()); } @Test @@ -396,10 +396,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mMockSurfaceControlTransactions.add(t); @@ -436,8 +432,7 @@ public class WindowDecorationTests extends ShellTestCase { windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId); verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height); verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface); - verify(mMockSurfaceControlViewHostFactory, Mockito.times(2)) - .create(any(), eq(defaultDisplay), any()); + verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any()); } @Test @@ -450,10 +445,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mMockSurfaceControlTransactions.add(t); @@ -473,8 +464,8 @@ public class WindowDecorationTests extends ShellTestCase { windowDecor.relayout(taskInfo); - verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); - verify(captionContainerSurfaceBuilder).setContainerLayer(); + final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl(); + verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface); // Width of the captionContainerSurface should match the width of TASK_BOUNDS verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); @@ -490,10 +481,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mMockSurfaceControlTransactions.add(t); @@ -511,9 +498,11 @@ public class WindowDecorationTests extends ShellTestCase { taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); - windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */); + mRelayoutParams.mApplyStartTransactionOnDraw = true; + windowDecor.relayout(taskInfo); - verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT); + verify(mMockWindowDecorViewHost).updateView(any(), any(), any(), + eq(mMockSurfaceControlStartT)); } @Test @@ -867,37 +856,52 @@ public class WindowDecorationTests extends ShellTestCase { } @Test - public void updateViewHost_applyTransactionOnDrawIsTrue_surfaceControlIsUpdated() { + public void relayout_applyTransactionOnDrawIsTrue_updatesViewWithDrawTransaction() { final TestWindowDecoration windowDecor = createWindowDecoration( - new TestRunningTaskInfoBuilder().build()); + new TestRunningTaskInfoBuilder() + .setVisible(true) + .setWindowingMode(WINDOWING_MODE_FREEFORM) + .build()); mRelayoutParams.mApplyStartTransactionOnDraw = true; mRelayoutResult.mRootView = mMockView; - windowDecor.updateViewHost(mRelayoutParams, mMockSurfaceControlStartT, mRelayoutResult); + windowDecor.relayout(windowDecor.mTaskInfo); - verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT); + verify(mMockWindowDecorViewHost) + .updateView(eq(mRelayoutResult.mRootView), any(), + eq(windowDecor.mTaskInfo.configuration), eq(mMockSurfaceControlStartT)); } @Test - public void updateViewHost_nullDrawTransaction_applyTransactionOnDrawIsTrue_throwsException() { + public void relayout_applyTransactionOnDrawIsTrue_asyncViewHostRendering_throwsException() { final TestWindowDecoration windowDecor = createWindowDecoration( - new TestRunningTaskInfoBuilder().build()); + new TestRunningTaskInfoBuilder() + .setVisible(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .build()); mRelayoutParams.mApplyStartTransactionOnDraw = true; + mRelayoutParams.mAsyncViewHost = true; mRelayoutResult.mRootView = mMockView; assertThrows(IllegalArgumentException.class, - () -> windowDecor.updateViewHost( - mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult)); + () -> windowDecor.relayout(windowDecor.mTaskInfo)); } @Test - public void updateViewHost_nullDrawTransaction_applyTransactionOnDrawIsFalse_doesNotThrow() { + public void relayout_asyncViewHostRendering() { final TestWindowDecoration windowDecor = createWindowDecoration( - new TestRunningTaskInfoBuilder().build()); - mRelayoutParams.mApplyStartTransactionOnDraw = false; + new TestRunningTaskInfoBuilder() + .setVisible(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .build()); + mRelayoutParams.mAsyncViewHost = true; mRelayoutResult.mRootView = mMockView; - windowDecor.updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult); + windowDecor.relayout(windowDecor.mTaskInfo); + + verify(mMockWindowDecorViewHost) + .updateViewAsync(eq(mRelayoutResult.mRootView), any(), + eq(windowDecor.mTaskInfo.configuration)); } @Test @@ -997,7 +1001,8 @@ public class WindowDecorationTests extends ShellTestCase { new MockObjectSupplier<>(mMockSurfaceControlTransactions, () -> mock(SurfaceControl.Transaction.class)), () -> mMockWindowContainerTransaction, () -> mMockTaskSurface, - mMockSurfaceControlViewHostFactory); + mMockSurfaceControlViewHostFactory, + mMockWindowDecorViewHostSupplier); } private class MockObjectSupplier<T> implements Supplier<T> { @@ -1037,16 +1042,20 @@ public class WindowDecorationTests extends ShellTestCase { Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, Supplier<SurfaceControl> surfaceControlSupplier, - SurfaceControlViewHostFactory surfaceControlViewHostFactory) { + SurfaceControlViewHostFactory surfaceControlViewHostFactory, + @NonNull WindowDecorViewHostSupplier windowDecorViewHostSupplier) { super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, windowContainerTransactionSupplier, surfaceControlSupplier, - surfaceControlViewHostFactory); + surfaceControlViewHostFactory, windowDecorViewHostSupplier); } @Override void relayout(ActivityManager.RunningTaskInfo taskInfo) { - relayout(taskInfo, false /* applyStartTransactionOnDraw */); + mRelayoutParams.mRunningTaskInfo = taskInfo; + mRelayoutParams.mLayoutResId = R.layout.caption_layout; + relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT, + mMockWindowContainerTransaction, mMockView, mRelayoutResult); } @Override @@ -1067,15 +1076,6 @@ public class WindowDecorationTests extends ShellTestCase { return super.inflateLayout(context, layoutResId); } - void relayout(ActivityManager.RunningTaskInfo taskInfo, - boolean applyStartTransactionOnDraw) { - mRelayoutParams.mRunningTaskInfo = taskInfo; - mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; - mRelayoutParams.mLayoutResId = R.layout.caption_layout; - relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT, - mMockWindowContainerTransaction, mMockView, mRelayoutResult); - } - private AdditionalViewContainer addTestViewContainer() { final Resources resources = mDecorWindowContext.getResources(); final int width = loadDimensionPixelSize(resources, mCaptionMenuWidthId); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt new file mode 100644 index 000000000000..1b2ce9e4df36 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt @@ -0,0 +1,222 @@ +/* + * 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.windowdecor.viewhost + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.SurfaceControl +import android.view.SurfaceControlViewHost +import android.view.View +import android.view.WindowManager +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertThrows +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify + + +/** + * Tests for [DefaultWindowDecorViewHost]. + * + * Build/Install/Run: + * atest WMShellUnitTests:DefaultWindowDecorViewHostTest + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) +class DefaultWindowDecorViewHostTest : ShellTestCase() { + + @Test + fun updateView_layoutInViewHost() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + assertThat(windowDecorViewHost.viewHost).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view) + } + + @Test + fun updateView_alreadyLaidOut_relayouts() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + val otherParams = WindowManager.LayoutParams(200, 200) + windowDecorViewHost.updateView( + view = view, + attrs = otherParams, + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view) + assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width) + .isEqualTo(otherParams.width) + } + + @Test + fun updateView_replacingView_throws() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + val otherView = View(context) + assertThrows(Exception::class.java) { + windowDecorViewHost.updateView( + view = otherView, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null + ) + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateView_clearsPendingAsyncJob() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val asyncView = View(context) + val syncView = View(context) + val asyncAttrs = WindowManager.LayoutParams(100, 100) + val syncAttrs = WindowManager.LayoutParams(200, 200) + + windowDecorViewHost.updateViewAsync( + view = asyncView, + attrs = asyncAttrs, + configuration = context.resources.configuration, + ) + + // No view host yet, since the coroutine hasn't run. + assertThat(windowDecorViewHost.viewHost).isNull() + + windowDecorViewHost.updateView( + view = syncView, + attrs = syncAttrs, + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + // Would run coroutine if it hadn't been cancelled. + advanceUntilIdle() + + assertThat(windowDecorViewHost.viewHost).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isNotNull() + // View host view/attrs should match the ones from the sync call, plus, since the + // sync/async were made with different views, if the job hadn't been cancelled there + // would've been an exception thrown as replacing views isn't allowed. + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(syncView) + assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width) + .isEqualTo(syncAttrs.width) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateViewAsync() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + val attrs = WindowManager.LayoutParams(100, 100) + + windowDecorViewHost.updateViewAsync( + view = view, + attrs = attrs, + configuration = context.resources.configuration, + ) + + assertThat(windowDecorViewHost.viewHost).isNull() + + advanceUntilIdle() + + assertThat(windowDecorViewHost.viewHost).isNotNull() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateViewAsync_clearsPendingAsyncJob() = runTest { + val windowDecorViewHost = createDefaultViewHost() + + val view = View(context) + windowDecorViewHost.updateViewAsync( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + ) + val otherView = View(context) + windowDecorViewHost.updateViewAsync( + view = otherView, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + ) + + advanceUntilIdle() + + assertThat(windowDecorViewHost.viewHost).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(otherView) + } + + @Test + fun release() = runTest { + val windowDecorViewHost = createDefaultViewHost() + + val view = View(context) + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + val t = mock(SurfaceControl.Transaction::class.java) + windowDecorViewHost.release(t) + + verify(windowDecorViewHost.viewHost!!).release() + verify(t).remove(windowDecorViewHost.surfaceControl) + } + + private fun CoroutineScope.createDefaultViewHost() = DefaultWindowDecorViewHost( + context = context, + mainScope = this, + display = context.display, + surfaceControlViewHostFactory = { c, d, wwm, s -> + spy(SurfaceControlViewHost(c, d, wwm, s)) + } + ) +}
\ No newline at end of file diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index bbd91fc19ee5..dcf5c5b46478 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -116,4 +116,7 @@ flag { description: "Flag for enabling NI SUPL message injection by carrier config" bug: "242105192" is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 1e5833564d39..9d56c77b097b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -520,8 +520,6 @@ public class Utils { if (android.webkit.Flags.updateServiceIpcWrapper()) { if (pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) { - // WebViewUpdateManager.getInstance() will not return null on devices with - // FEATURE_WEBVIEW. provider = WebViewUpdateManager.getInstance().getDefaultWebViewPackage(); } } else { diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index d39b5645109d..b491b5a146e1 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -223,6 +223,7 @@ public class SettingsBackupTest { Settings.Global.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE, Settings.Global.ENABLE_DISKSTATS_LOGGING, Settings.Global.ENABLE_EPHEMERAL_FEATURE, + Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED, Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED, Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, diff --git a/packages/SystemUI/animation/lib/Android.bp b/packages/SystemUI/animation/lib/Android.bp new file mode 100644 index 000000000000..4324d463c276 --- /dev/null +++ b/packages/SystemUI/animation/lib/Android.bp @@ -0,0 +1,41 @@ +// 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 { + default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_", + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +java_library { + name: "PlatformAnimationLib-server", + srcs: [ + "src/com/android/systemui/animation/server/*.java", + ":PlatformAnimationLib-aidl", + ], + static_libs: [ + "WindowManager-Shell-shared", + ], +} + +filegroup { + name: "PlatformAnimationLib-aidl", + srcs: [ + "src/**/*.aidl", + ], +} diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java new file mode 100644 index 000000000000..3cbb688ce7ca --- /dev/null +++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java @@ -0,0 +1,424 @@ +/* + * 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.animation.server; + +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; + +import android.Manifest; +import android.annotation.Nullable; +import android.app.TaskInfo; +import android.content.ComponentName; +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; +import android.view.SurfaceControl; +import android.window.IRemoteTransition; +import android.window.IRemoteTransitionFinishedCallback; +import android.window.RemoteTransition; +import android.window.TransitionFilter; +import android.window.TransitionInfo; +import android.window.TransitionInfo.Change; +import android.window.WindowAnimationState; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; +import com.android.systemui.animation.shared.IOriginTransitions; +import com.android.wm.shell.shared.ShellTransitions; +import com.android.wm.shell.shared.TransitionUtil; + +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.function.Predicate; + +/** An implementation of the {@link IOriginTransitions}. */ +public class IOriginTransitionsImpl extends IOriginTransitions.Stub { + private static final boolean DEBUG = true; + private static final String TAG = "OriginTransitions"; + + private final Object mLock = new Object(); + private final ShellTransitions mShellTransitions; + private final Context mContext; + + @GuardedBy("mLock") + private final Map<IBinder, OriginTransitionRecord> mRecords = new ArrayMap<>(); + + public IOriginTransitionsImpl(Context context, ShellTransitions shellTransitions) { + mShellTransitions = shellTransitions; + mContext = context; + } + + @Override + public RemoteTransition makeOriginTransition( + RemoteTransition launchTransition, RemoteTransition returnTransition) + throws RemoteException { + if (DEBUG) { + Log.d( + TAG, + "makeOriginTransition: (" + launchTransition + ", " + returnTransition + ")"); + } + enforceRemoteTransitionPermission(); + synchronized (mLock) { + OriginTransitionRecord record = + new OriginTransitionRecord(launchTransition, returnTransition); + mRecords.put(record.getToken(), record); + return record.asLaunchableTransition(); + } + } + + @Override + public void cancelOriginTransition(RemoteTransition originTransition) { + if (DEBUG) { + Log.d(TAG, "cancelOriginTransition: " + originTransition); + } + enforceRemoteTransitionPermission(); + synchronized (mLock) { + if (!mRecords.containsKey(originTransition.asBinder())) { + return; + } + mRecords.get(originTransition.asBinder()).destroy(); + } + } + + private void enforceRemoteTransitionPermission() { + mContext.enforceCallingPermission( + Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, + "Missing permission " + + Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS); + } + + public void dump(IndentingPrintWriter ipw) { + ipw.println("IOriginTransitionsImpl"); + ipw.println("Active records:"); + ipw.increaseIndent(); + synchronized (mLock) { + if (mRecords.isEmpty()) { + ipw.println("none"); + } else { + for (OriginTransitionRecord record : mRecords.values()) { + record.dump(ipw); + } + } + } + ipw.decreaseIndent(); + } + + /** + * An {@link IRemoteTransition} that delegates animation to another {@link IRemoteTransition} + * and notify callbacks when the transition starts. + */ + private static class RemoteTransitionDelegate extends IRemoteTransition.Stub { + private final IRemoteTransition mTransition; + private final Predicate<TransitionInfo> mOnStarting; + private final Executor mExecutor; + + RemoteTransitionDelegate( + Executor executor, + IRemoteTransition transition, + Predicate<TransitionInfo> onStarting) { + mExecutor = executor; + mTransition = transition; + mOnStarting = onStarting; + } + + @Override + public void startAnimation( + IBinder token, + TransitionInfo info, + SurfaceControl.Transaction t, + IRemoteTransitionFinishedCallback finishCallback) + throws RemoteException { + if (DEBUG) { + Log.d(TAG, "startAnimation: " + info); + } + if (!mOnStarting.test(info)) { + Log.w(TAG, "Skipping cancelled transition " + mTransition); + t.addTransactionCommittedListener( + mExecutor, + () -> { + try { + finishCallback.onTransitionFinished(null, null); + } catch (RemoteException e) { + Log.e(TAG, "Unable to report finish.", e); + } + }) + .apply(); + return; + } + mTransition.startAnimation(token, info, t, finishCallback); + } + + @Override + public void mergeAnimation( + IBinder transition, + TransitionInfo info, + SurfaceControl.Transaction t, + IBinder mergeTarget, + IRemoteTransitionFinishedCallback finishCallback) + throws RemoteException { + if (DEBUG) { + Log.d(TAG, "mergeAnimation: " + info); + } + mTransition.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + } + + @Override + public void takeOverAnimation( + IBinder transition, + TransitionInfo info, + SurfaceControl.Transaction t, + IRemoteTransitionFinishedCallback finishCallback, + WindowAnimationState[] states) + throws RemoteException { + if (DEBUG) { + Log.d(TAG, "takeOverAnimation: " + info); + } + mTransition.takeOverAnimation(transition, info, t, finishCallback, states); + } + + @Override + public void onTransitionConsumed(IBinder transition, boolean aborted) + throws RemoteException { + if (DEBUG) { + Log.d(TAG, "onTransitionConsumed: aborted=" + aborted); + } + mTransition.onTransitionConsumed(transition, aborted); + } + + @Override + public String toString() { + return "RemoteTransitionDelegate{transition=" + mTransition + "}"; + } + } + + /** A data record containing the origin transition pieces. */ + private class OriginTransitionRecord implements IBinder.DeathRecipient { + private final RemoteTransition mWrappedLaunchTransition; + private final RemoteTransition mWrappedReturnTransition; + + @GuardedBy("mLock") + private boolean mDestroyed; + + OriginTransitionRecord(RemoteTransition launchTransition, RemoteTransition returnTransition) + throws RemoteException { + mWrappedLaunchTransition = wrap(launchTransition, this::onLaunchTransitionStarting); + mWrappedReturnTransition = wrap(returnTransition, this::onReturnTransitionStarting); + linkToDeath(); + } + + private boolean onLaunchTransitionStarting(TransitionInfo info) { + synchronized (mLock) { + if (mDestroyed) { + return false; + } + TransitionFilter filter = createFilterForReverseTransition(info); + if (filter != null) { + if (DEBUG) { + Log.d(TAG, "Registering filter " + filter); + } + mShellTransitions.registerRemote(filter, mWrappedReturnTransition); + } + return true; + } + } + + private boolean onReturnTransitionStarting(TransitionInfo info) { + synchronized (mLock) { + if (mDestroyed) { + return false; + } + // Clean up stuff. + destroy(); + return true; + } + } + + public void destroy() { + synchronized (mLock) { + if (mDestroyed) { + // Already destroyed. + return; + } + if (DEBUG) { + Log.d(TAG, "Destroying origin transition record " + this); + } + mDestroyed = true; + unlinkToDeath(); + mShellTransitions.unregisterRemote(mWrappedReturnTransition); + mRecords.remove(getToken()); + } + } + + private void linkToDeath() throws RemoteException { + asDelegate(mWrappedLaunchTransition).mTransition.asBinder().linkToDeath(this, 0); + asDelegate(mWrappedReturnTransition).mTransition.asBinder().linkToDeath(this, 0); + } + + private void unlinkToDeath() { + asDelegate(mWrappedLaunchTransition).mTransition.asBinder().unlinkToDeath(this, 0); + asDelegate(mWrappedReturnTransition).mTransition.asBinder().unlinkToDeath(this, 0); + } + + public IBinder getToken() { + return asLaunchableTransition().asBinder(); + } + + public RemoteTransition asLaunchableTransition() { + return mWrappedLaunchTransition; + } + + @Override + public void binderDied() { + destroy(); + } + + @Override + public String toString() { + return "OriginTransitionRecord{launch=" + + mWrappedReturnTransition + + ", return=" + + mWrappedReturnTransition + + "}"; + } + + public void dump(IndentingPrintWriter ipw) { + synchronized (mLock) { + ipw.println("OriginTransitionRecord"); + ipw.increaseIndent(); + ipw.println("mDestroyed: " + mDestroyed); + ipw.println("Launch transition:"); + ipw.increaseIndent(); + ipw.println(mWrappedLaunchTransition); + ipw.decreaseIndent(); + ipw.println("Return transition:"); + ipw.increaseIndent(); + ipw.println(mWrappedReturnTransition); + ipw.decreaseIndent(); + ipw.decreaseIndent(); + } + } + + private static RemoteTransitionDelegate asDelegate(RemoteTransition transition) { + return (RemoteTransitionDelegate) transition.getRemoteTransition(); + } + + private RemoteTransition wrap( + RemoteTransition transition, Predicate<TransitionInfo> onStarting) { + return new RemoteTransition( + new RemoteTransitionDelegate( + mContext.getMainExecutor(), + transition.getRemoteTransition(), + onStarting), + transition.getDebugName()); + } + + @Nullable + private static TransitionFilter createFilterForReverseTransition(TransitionInfo info) { + TaskInfo launchingTaskInfo = null; + TaskInfo launchedTaskInfo = null; + ComponentName launchingActivity = null; + ComponentName launchedActivity = null; + for (Change change : info.getChanges()) { + int mode = change.getMode(); + TaskInfo taskInfo = change.getTaskInfo(); + ComponentName activity = change.getActivityComponent(); + if (TransitionUtil.isClosingMode(mode) + && launchingTaskInfo == null + && taskInfo != null) { + // Found the launching task! + launchingTaskInfo = taskInfo; + } else if (TransitionUtil.isOpeningMode(mode) + && launchedTaskInfo == null + && taskInfo != null) { + // Found the launched task! + launchedTaskInfo = taskInfo; + } else if (TransitionUtil.isClosingMode(mode) + && launchingActivity == null + && activity != null) { + // Found the launching activity + launchingActivity = activity; + } else if (TransitionUtil.isOpeningMode(mode) + && launchedActivity == null + && activity != null) { + // Found the launched activity! + launchedActivity = activity; + } + } + if (DEBUG) { + Log.d( + TAG, + "createFilterForReverseTransition: launchingTaskInfo=" + + launchingTaskInfo + + ", launchedTaskInfo=" + + launchedTaskInfo + + ", launchingActivity=" + + launchedActivity + + ", launchedActivity=" + + launchedActivity); + } + if (launchingTaskInfo == null && launchingActivity == null) { + Log.w( + TAG, + "createFilterForReverseTransition: unable to find launching task or" + + " launching activity!"); + return null; + } + if (launchedTaskInfo == null && launchedActivity == null) { + Log.w( + TAG, + "createFilterForReverseTransition: unable to find launched task or launched" + + " activity!"); + return null; + } + if (launchedTaskInfo != null && launchedTaskInfo.launchCookies.isEmpty()) { + Log.w( + TAG, + "createFilterForReverseTransition: skipped - launched task has no launch" + + " cookie!"); + return null; + } + TransitionFilter filter = new TransitionFilter(); + filter.mTypeSet = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK}; + + // The opening activity of the return transition must match the activity we just closed. + TransitionFilter.Requirement req1 = new TransitionFilter.Requirement(); + req1.mModes = new int[] {TRANSIT_OPEN, TRANSIT_TO_FRONT}; + req1.mTopActivity = + launchingActivity == null ? launchingTaskInfo.topActivity : launchingActivity; + + TransitionFilter.Requirement req2 = new TransitionFilter.Requirement(); + req2.mModes = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK}; + if (launchedTaskInfo != null) { + // For task transitions, the closing task's cookie must match the task we just + // launched. + req2.mLaunchCookie = launchedTaskInfo.launchCookies.get(0); + } else { + // For activity transitions, the closing activity of the return transition must + // match + // the activity we just launched. + req2.mTopActivity = launchedActivity; + } + + filter.mRequirements = new TransitionFilter.Requirement[] {req1, req2}; + return filter; + } + } +} diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/shared/IOriginTransitions.aidl b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/shared/IOriginTransitions.aidl new file mode 100644 index 000000000000..31cca701ca9d --- /dev/null +++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/shared/IOriginTransitions.aidl @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.animation.shared; + +import android.window.RemoteTransition; + +/** + * An interface for an app to link a launch transition and a return transition together into an + * origin transition. + */ +interface IOriginTransitions { + + /** + * Create a new "origin transition" which wraps a launch transition and a return transition. + * The returned {@link RemoteTransition} is expected to be passed to + * {@link ActivityOptions#makeRemoteTransition(RemoteTransition)} to create an + * {@link ActivityOptions} and being used to launch an intent. When being used with + * {@link ActivityOptions}, the launch transition will be triggered for launching the intent, + * and the return transition will be remembered and triggered for returning from the launched + * activity. + */ + RemoteTransition makeOriginTransition(in RemoteTransition launchTransition, + in RemoteTransition returnTransition) = 1; + + /** + * Cancels an origin transition. Any parts not yet played will no longer be triggered, and the + * origin transition object will reset to a single frame animation. + */ + void cancelOriginTransition(in RemoteTransition originTransition) = 2; +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 1287993081fa..489e24e8b328 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -63,6 +63,7 @@ import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.res.R import kotlin.time.Duration.Companion.milliseconds import kotlin.time.DurationUnit @@ -103,7 +104,7 @@ fun PinPad( columns = columns, verticalSpacing = verticalSpacing, horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp), - modifier = modifier.focusRequester(focusRequester) + modifier = modifier.focusRequester(focusRequester).sysuiResTag("pin_pad_grid") ) { repeat(9) { index -> DigitButton( @@ -128,6 +129,7 @@ fun PinPad( onLongPressed = viewModel::onBackspaceButtonLongPressed, appearance = backspaceButtonAppearance, scaling = buttonScaleAnimatables[9]::value, + elementId = "delete_button" ) DigitButton( @@ -150,6 +152,7 @@ fun PinPad( onClicked = viewModel::onAuthenticateButtonClicked, appearance = confirmButtonAppearance, scaling = buttonScaleAnimatables[11]::value, + elementId = "key_enter" ) } } @@ -192,6 +195,7 @@ private fun ActionButton( icon: Icon, isInputEnabled: Boolean, onClicked: () -> Unit, + elementId: String, onLongPressed: (() -> Unit)? = null, appearance: ActionButtonAppearance, scaling: () -> Float, @@ -217,6 +221,7 @@ private fun ActionButton( backgroundColor = backgroundColor, foregroundColor = foregroundColor, isAnimationEnabled = true, + elementId = elementId, modifier = Modifier.graphicsLayer { alpha = hiddenAlpha @@ -240,6 +245,7 @@ private fun PinPadButton( foregroundColor: Color, isAnimationEnabled: Boolean, modifier: Modifier = Modifier, + elementId: String? = null, onLongPressed: (() -> Unit)? = null, onPointerDown: (() -> Unit)? = null, content: @Composable (contentColor: () -> Color) -> Unit, @@ -321,7 +327,8 @@ private fun PinPadButton( } false } - }, + } + .thenIf(elementId != null) { Modifier.sysuiResTag(elementId!!) }, ) { content(contentColor::value) } 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 d34295ea1d22..fa92bef34f38 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 @@ -64,6 +64,7 @@ import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -146,9 +147,7 @@ constructor( } @Composable - override fun SceneScope.Content( - modifier: Modifier, - ) { + override fun SceneScope.Content(modifier: Modifier) { QuickSettingsScene( notificationStackScrollView = notificationStackScrollView.get(), viewModelFactory = contentViewModelFactory, @@ -199,13 +198,17 @@ private fun SceneScope.QuickSettingsScene( onDispose { notificationsPlaceholderViewModel.setAlphaForBrightnessMirror(1f) } } + val shadeHorizontalPadding = + dimensionResource(id = R.dimen.notification_panel_margin_horizontal) + BrightnessMirror( viewModel = brightnessMirrorViewModel, qsSceneAdapter = viewModel.qsSceneAdapter, modifier = Modifier.thenIf(cutoutLocation != CutoutLocation.CENTER) { - Modifier.displayCutoutPadding() - } + Modifier.displayCutoutPadding() + } + .padding(horizontal = shadeHorizontalPadding), ) val shouldPunchHoleBehindScrim = @@ -224,9 +227,7 @@ private fun SceneScope.QuickSettingsScene( // scene (and not the one under it) during a scene transition. Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) } - .thenIf(cutoutLocation != CutoutLocation.CENTER) { - Modifier.displayCutoutPadding() - }, + .thenIf(cutoutLocation != CutoutLocation.CENTER) { Modifier.displayCutoutPadding() } ) { val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle() val isCustomizerShowing by @@ -235,11 +236,7 @@ private fun SceneScope.QuickSettingsScene( viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle() val screenHeight = LocalRawScreenHeight.current - BackHandler( - enabled = isCustomizing, - ) { - viewModel.qsSceneAdapter.requestCloseCustomizer() - } + BackHandler(enabled = isCustomizing) { viewModel.qsSceneAdapter.requestCloseCustomizer() } val collapsedHeaderHeight = with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } @@ -276,13 +273,13 @@ private fun SceneScope.QuickSettingsScene( animateDpAsState( targetValue = if (isCustomizing) 0.dp else navBarBottomHeight, animationSpec = tween(customizingAnimationDuration), - label = "animateQSSceneBottomPaddingAsState" + label = "animateQSSceneBottomPaddingAsState", ) val topPadding by animateDpAsState( targetValue = if (isCustomizing) ShadeHeader.Dimensions.CollapsedHeight else 0.dp, animationSpec = tween(customizingAnimationDuration), - label = "animateQSSceneTopPaddingAsState" + label = "animateQSSceneTopPaddingAsState", ) LaunchedEffect(navBarBottomHeight, density) { @@ -313,18 +310,15 @@ private fun SceneScope.QuickSettingsScene( Modifier.fillMaxSize() .padding( top = topPadding.coerceAtLeast(0.dp), - bottom = bottomPadding.coerceAtLeast(0.dp) - ) + bottom = bottomPadding.coerceAtLeast(0.dp), + ), ) { Box(modifier = Modifier.fillMaxSize().weight(1f)) { val shadeHeaderAndQuickSettingsModifier = if (isCustomizerShowing) { Modifier.fillMaxHeight().align(Alignment.TopCenter) } else { - Modifier.verticalScroll( - scrollState, - enabled = isScrollable, - ) + Modifier.verticalScroll(scrollState, enabled = isScrollable) .clipScrollableContainer(Orientation.Horizontal) .fillMaxWidth() .wrapContentHeight(unbounded = true) @@ -333,7 +327,7 @@ private fun SceneScope.QuickSettingsScene( Column( modifier = - shadeHeaderAndQuickSettingsModifier.sysuiResTag("expanded_qs_scroll_view"), + shadeHeaderAndQuickSettingsModifier.sysuiResTag("expanded_qs_scroll_view") ) { when (LocalWindowSizeClass.current.widthSizeClass) { WindowWidthSizeClass.Compact -> @@ -345,7 +339,7 @@ private fun SceneScope.QuickSettingsScene( expandFrom = Alignment.Top, ) + slideInVertically( - animationSpec = tween(customizingAnimationDuration), + animationSpec = tween(customizingAnimationDuration) ) + fadeIn(tween(customizingAnimationDuration)), exit = @@ -354,7 +348,7 @@ private fun SceneScope.QuickSettingsScene( shrinkTowards = Alignment.Top, ) + slideOutVertically( - animationSpec = tween(customizingAnimationDuration), + animationSpec = tween(customizingAnimationDuration) ) + fadeOut(tween(customizingAnimationDuration)), ) { @@ -382,7 +376,7 @@ private fun SceneScope.QuickSettingsScene( viewModel.qsSceneAdapter, { viewModel.qsSceneAdapter.qsHeight }, isSplitShade = false, - modifier = Modifier.layoutId(QSMediaMeasurePolicy.LayoutId.QS) + modifier = Modifier.layoutId(QSMediaMeasurePolicy.LayoutId.QS), ) MediaCarousel( @@ -400,13 +394,12 @@ private fun SceneScope.QuickSettingsScene( { mediaOffset.roundToPx() }, ) } - if (mediaInRow) { - Layout( - content = content, - measurePolicy = landscapeQsMediaMeasurePolicy, - ) - } else { - content() + Box(modifier = Modifier.padding(horizontal = shadeHorizontalPadding)) { + if (mediaInRow) { + Layout(content = content, measurePolicy = landscapeQsMediaMeasurePolicy) + } else { + content() + } } } } @@ -417,13 +410,18 @@ private fun SceneScope.QuickSettingsScene( customizingAnimationDuration = customizingAnimationDuration, lifecycleOwner = lifecycleOwner, modifier = - Modifier.align(Alignment.CenterHorizontally).sysuiResTag("qs_footer_actions"), + Modifier.align(Alignment.CenterHorizontally) + .sysuiResTag("qs_footer_actions") + .padding(horizontal = shadeHorizontalPadding), ) } HeadsUpNotificationSpace( stackScrollView = notificationStackScrollView, viewModel = notificationsPlaceholderViewModel, - modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding(), + modifier = + Modifier.align(Alignment.BottomCenter) + .navigationBarsPadding() + .padding(horizontal = shadeHorizontalPadding), isPeekFromBottom = true, ) NotificationScrollingStack( @@ -435,15 +433,18 @@ private fun SceneScope.QuickSettingsScene( shouldIncludeHeadsUpSpace = false, shadeMode = ShadeMode.Single, modifier = - Modifier.fillMaxWidth().offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }, + Modifier.fillMaxWidth() + .offset { IntOffset(x = 0, y = screenHeight.roundToInt()) } + .padding(horizontal = shadeHorizontalPadding), ) NotificationStackCutoffGuideline( stackScrollView = notificationStackScrollView, viewModel = notificationsPlaceholderViewModel, modifier = - Modifier.align(Alignment.BottomCenter).navigationBarsPadding().offset { - IntOffset(x = 0, y = screenHeight.roundToInt()) - } + Modifier.align(Alignment.BottomCenter) + .navigationBarsPadding() + .offset { IntOffset(x = 0, y = screenHeight.roundToInt()) } + .padding(horizontal = shadeHorizontalPadding), ) } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt index de55e2fb7657..ea6f208d6bb9 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt @@ -31,16 +31,23 @@ import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.TransitionRecordingSpec import com.android.compose.animation.scene.featureOfElement import com.android.compose.animation.scene.recordTransition +import org.junit.ClassRule import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import platform.test.motion.compose.ComposeFeatureCaptures import platform.test.motion.compose.createComposeMotionTestRule import platform.test.motion.testing.createGoldenPathManager +import platform.test.screenshot.ResetDeviceEmulationRule @RunWith(AndroidJUnit4::class) @MotionTest class AnchoredSizeTest { + + companion object { + @JvmField @ClassRule val cleanupRule: ResetDeviceEmulationRule = ResetDeviceEmulationRule() + } + private val goldenPaths = createGoldenPathManager("frameworks/base/packages/SystemUI/compose/scene/tests/goldens") diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt index b3a12a69d1af..1e79112eefe3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt @@ -22,7 +22,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.widgets.CommunalAppWidgetHost -import com.android.systemui.communal.widgets.CommunalAppWidgetHostView import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope @@ -61,29 +60,11 @@ class CommunalAppWidgetHostTest : SysuiTestCase() { context = context, backgroundScope = kosmos.applicationCoroutineScope, hostId = 116, - interactionHandler = mock(), - looper = testableLooper.looper, logBuffer = logcatLogBuffer("CommunalAppWidgetHostTest"), ) } @Test - fun createViewForCommunal_returnCommunalAppWidgetView() { - val appWidgetId = 789 - val view = - underTest.createViewForCommunal( - context = context, - appWidgetId = appWidgetId, - appWidget = null - ) - testableLooper.processAllMessages() - - assertThat(view).isInstanceOf(CommunalAppWidgetHostView::class.java) - assertThat(view).isNotNull() - assertThat(view.appWidgetId).isEqualTo(appWidgetId) - } - - @Test fun appWidgetIdToRemove_emit() = testScope.runTest { val appWidgetIdToRemove by collectLastValue(underTest.appWidgetIdToRemove) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index e4098ae885ac..2fd94e2016aa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -507,6 +507,46 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() @Test @DisableSceneContainer + fun alphaFromShadeExpansion_doesNotEmitWhenOccludedTransitionRunning() = + testScope.runTest { + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + + val alpha by collectLastValue(underTest.alpha(viewState)) + shadeTestUtil.setQsExpansion(0f) + runCurrent() + assertThat(alpha).isEqualTo(1f) + + keyguardTransitionRepository.sendTransitionSteps( + listOf( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + transitionState = TransitionState.STARTED, + value = 0f, + ), + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + transitionState = TransitionState.RUNNING, + value = 0.8f, + ), + ), + testScope, + ) + // Alpha should be 0f from the above transition + assertThat(alpha).isEqualTo(0f) + + shadeTestUtil.setQsExpansion(0.5f) + // Alpha should remain unchanged + assertThat(alpha).isEqualTo(0f) + } + + @Test + @DisableSceneContainer fun alphaFromShadeExpansion_doesNotEmitWhenLockscreenToDreamTransitionRunning() = testScope.runTest { keyguardTransitionRepository.sendTransitionSteps( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt index 2a2817b9af73..ad2b23e49536 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt @@ -88,6 +88,26 @@ class ShadeModeInteractorImplTest : SysuiTestCase() { } @Test + @EnableFlags(DualShade.FLAG_NAME) + fun isDualShade_flagEnabled_true() = + testScope.runTest { + // Initiate collection. + val shadeMode by collectLastValue(underTest.shadeMode) + + assertThat(underTest.isDualShade).isTrue() + } + + @Test + @DisableFlags(DualShade.FLAG_NAME) + fun isDualShade_flagDisabled_false() = + testScope.runTest { + // Initiate collection. + val shadeMode by collectLastValue(underTest.shadeMode) + + assertThat(underTest.isDualShade).isFalse() + } + + @Test fun getTopEdgeSplitFraction_narrowScreen_splitInHalf() = testScope.runTest { // Ensure isShadeLayoutWide is collected. 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 469a7bc6d9fb..305367213571 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 @@ -38,7 +38,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun import com.android.systemui.statusbar.phone.ConfigurationControllerImpl -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone +import com.android.systemui.statusbar.notification.HeadsUpManagerPhone import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.DelayableExecutor diff --git a/packages/SystemUI/res/raw/trackpad_recent_apps_edu.json b/packages/SystemUI/res/raw/trackpad_recent_apps_edu.json new file mode 100644 index 000000000000..c2e945d38a70 --- /dev/null +++ b/packages/SystemUI/res/raw/trackpad_recent_apps_edu.json @@ -0,0 +1 @@ +{"v":"5.12.1","fr":60,"ip":0,"op":511,"w":554,"h":564,"nm":"Trackpad-JSON_Recents-EDU","ddd":0,"assets":[{"id":"comp_0","nm":"Recents_EDU Loop","fr":60,"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"CNTL || playback","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":0,"k":0},"y":{"a":0,"k":0}},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Picker","np":3,"mn":"Pseudo/@@WcSiov6sT3a4/s0XPKYEOQ","ix":1,"en":1,"ef":[{"ty":7,"nm":"Menu","mn":"Pseudo/@@WcSiov6sT3a4/s0XPKYEOQ-0001","ix":1,"v":{"a":0,"k":2}}]},{"ty":5,"nm":"OUTPUT","np":3,"mn":"ADBE Slider Control","ix":2,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"k":[{"s":[0],"t":142,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.001],"t":143,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.002],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.003],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.004],"t":146,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.006],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.008],"t":148,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.01],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.012],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.016],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.02],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.025],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.031],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.038],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.047],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.059],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.073],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.091],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.116],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.15],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.196],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.249],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.306],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.366],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.425],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.481],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.53],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.575],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.614],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.648],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.678],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.706],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.73],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.752],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.772],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.79],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.807],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.822],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.836],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.849],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.861],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.873],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.883],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.892],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.901],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.91],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.917],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.925],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.931],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.937],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.943],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.949],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.954],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.958],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.963],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.967],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.97],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.974],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.977],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.98],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.983],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.985],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.987],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.989],"t":205,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.991],"t":206,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.993],"t":207,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.994],"t":208,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.996],"t":209,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.997],"t":210,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.998],"t":211,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.998],"t":212,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.999],"t":213,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1],"t":215,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.009],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.038],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.093],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.193],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.4],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.636],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.739],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.8],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.84],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.871],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.894],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.912],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.928],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.94],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.951],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.959],"t":266,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.967],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.973],"t":268,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.979],"t":269,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.983],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.987],"t":271,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.99],"t":272,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.993],"t":273,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.995],"t":274,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.997],"t":275,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.998],"t":276,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.999],"t":278,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2],"t":380,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.009],"t":381,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.038],"t":382,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.093],"t":383,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.193],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.4],"t":385,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.636],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.739],"t":387,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.8],"t":388,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.84],"t":389,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.871],"t":390,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.894],"t":391,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.912],"t":392,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.928],"t":393,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.94],"t":394,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.951],"t":395,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.959],"t":396,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.967],"t":397,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.973],"t":398,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.979],"t":399,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.983],"t":400,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.987],"t":401,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.99],"t":402,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.993],"t":403,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.995],"t":404,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.997],"t":405,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.999],"t":408,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}}]},{"ty":5,"nm":"Keys","np":3,"mn":"ADBE Slider Control","ix":3,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.831],"y":[0.109]},"o":{"x":[0.458],"y":[0.053]},"t":142,"s":[0]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.15],"y":[0.43]},"t":161,"s":[0.15]},{"t":217,"s":[1],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":250,"s":[1]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":255,"s":[1.4]},{"t":280,"s":[2],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[2]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":385,"s":[2.4]},{"t":410,"s":[3]}]}}]},{"ty":5,"nm":"State (holds)","np":3,"mn":"ADBE Slider Control","ix":4,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":0,"k":0}}]}],"shapes":[],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null :: Taskbar drop","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"k":[{"s":[252,278,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,278.45,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,279.615,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,281.252,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,283.166,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,287.233,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,289.181,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,290.982,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,292.599,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,294.012,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,295.216,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,296.216,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,297.023,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,297.655,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.131,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.474,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.705,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.465,0],"t":212,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.226,0],"t":215,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298,0],"t":377,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.382,0],"t":378,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,299.372,0],"t":379,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,300.764,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,302.391,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,305.848,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,307.504,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,309.035,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,310.409,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,311.611,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,312.634,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,313.483,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,314.169,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,314.706,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.112,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.403,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.717,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.474,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.192,0],"t":406,"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,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"Taskbar Lofi","parent":3,"refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":134,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":143,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":432,"s":[100]},{"t":444,"s":[0]}]},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":0,"k":0},"y":{"k":[{"s":[26.984],"t":127,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.971],"t":128,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.95],"t":129,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.921],"t":130,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.882],"t":131,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.83],"t":132,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.765],"t":133,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.685],"t":134,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.589],"t":135,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.478],"t":136,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.349],"t":137,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.205],"t":138,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.072],"t":139,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.926],"t":140,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.764],"t":141,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.589],"t":142,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.397],"t":143,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.187],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.959],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.711],"t":146,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.44],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.146],"t":148,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.826],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.479],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.1],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.686],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.236],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[21.745],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[21.207],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[20.616],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.967],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.248],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.457],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.578],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.602],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.514],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.303],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[12.954],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[11.477],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[9.885],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[8.215],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[6.526],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[4.878],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[3.338],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.928],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.659],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.475],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-1.485],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-2.388],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-3.192],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-3.911],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-4.556],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-5.136],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-5.662],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-6.135],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-6.563],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-6.951],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-7.303],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-7.622],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-7.913],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.175],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.413],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.628],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.823],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.998],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.155],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.296],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.42],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.531],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.627],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.711],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.783],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.843],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.893],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.933],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.963],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.984],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.996],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}},"a":{"a":0,"k":[91,15,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]}]}}]}],"w":182,"h":30,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":3,"nm":"Focus Task :: Lift & Drop","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":0,"k":252},"y":{"k":[{"s":[157.385],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.28],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.128],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.026],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.901],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.75],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.564],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.335],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.054],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.706],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.275],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[154.73],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[154.03],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[153.103],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[151.8],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[150.035],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[148.047],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.867],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[143.589],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[141.341],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[139.241],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[137.346],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[135.666],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[134.185],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[132.878],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[131.718],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[130.684],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[129.755],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[128.916],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[128.155],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[127.462],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[126.829],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[126.249],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[125.715],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[125.221],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[124.765],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[124.343],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[123.951],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[123.587],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[123.249],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.934],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.641],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.369],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.114],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.877],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.657],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.452],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.26],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.082],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.918],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.764],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.623],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.492],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.371],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.261],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.158],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.065],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.98],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.903],"t":205,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.835],"t":206,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.718],"t":208,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.629],"t":210,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.51],"t":215,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.5],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.746],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.54],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.071],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[124.808],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[130.5],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[136.982],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[139.835],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[141.489],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[142.613],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[143.442],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[144.082],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[144.593],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.01],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.354],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.642],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.884],"t":266,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.089],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.262],"t":268,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.409],"t":269,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.534],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.638],"t":271,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.725],"t":272,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.857],"t":274,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147],"t":380,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147.094],"t":381,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147.397],"t":382,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147.982],"t":383,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[149.027],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[151.2],"t":385,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[153.675],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[154.764],"t":387,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.396],"t":388,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.825],"t":389,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.141],"t":390,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.386],"t":391,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.581],"t":392,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.74],"t":393,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.871],"t":394,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.981],"t":395,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.074],"t":396,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.218],"t":398,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.362],"t":401,"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,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"matte","parent":5,"td":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":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"k":[{"s":[503.613,314.758],"t":144,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[503.134,314.459],"t":146,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.832,314.27],"t":147,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.464,314.04],"t":148,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.025,313.765],"t":149,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[501.487,313.429],"t":150,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[500.824,313.015],"t":151,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[500.023,312.514],"t":152,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[499.032,311.895],"t":153,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[497.818,311.136],"t":154,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[496.328,310.205],"t":155,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[494.484,309.053],"t":156,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[492.194,307.621],"t":157,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[489.307,305.817],"t":158,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[485.592,303.495],"t":159,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[480.67,300.419],"t":160,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[473.76,296.1],"t":161,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[464.395,290.247],"t":162,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[453.849,283.656],"t":163,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[442.286,276.429],"t":164,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[430.198,268.874],"t":165,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[418.274,261.421],"t":166,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[407.131,254.457],"t":167,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[397.077,248.173],"t":168,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[388.165,242.603],"t":169,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[380.31,237.694],"t":170,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[373.373,233.358],"t":171,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[367.218,229.511],"t":172,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[361.732,226.082],"t":173,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[356.803,223.002],"t":174,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[352.354,220.221],"t":175,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[348.318,217.699],"t":176,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[344.643,215.402],"t":177,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[341.283,213.302],"t":178,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[338.205,211.378],"t":179,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[335.37,209.606],"t":180,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[332.752,207.97],"t":181,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[330.33,206.456],"t":182,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[328.092,205.058],"t":183,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[326.012,203.757],"t":184,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[324.082,202.552],"t":185,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[322.291,201.432],"t":186,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[320.617,200.386],"t":187,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[319.062,199.414],"t":188,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[317.618,198.512],"t":189,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[316.267,197.667],"t":190,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[315.013,196.883],"t":191,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[313.845,196.153],"t":192,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[312.756,195.472],"t":193,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[311.738,194.837],"t":194,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[310.793,194.246],"t":195,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[309.921,193.7],"t":196,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[309.107,193.192],"t":197,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[308.359,192.724],"t":198,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[307.663,192.289],"t":199,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[307.02,191.888],"t":200,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[306.436,191.522],"t":201,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[305.891,191.182],"t":202,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[305.399,190.874],"t":203,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[304.946,190.591],"t":204,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[304.539,190.337],"t":205,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[304.178,190.112],"t":206,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.85,189.906],"t":207,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.555,189.722],"t":208,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.306,189.566],"t":209,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.082,189.427],"t":210,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.892,189.308],"t":211,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.617,189.135],"t":213,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.4,189],"t":250,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.247,188.904],"t":251,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[301.752,188.595],"t":252,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[300.798,187.999],"t":253,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[299.093,186.933],"t":254,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[295.546,184.716],"t":255,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[291.506,182.192],"t":256,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[289.729,181.08],"t":257,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[288.698,180.436],"t":258,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.998,179.999],"t":259,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.481,179.676],"t":260,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.082,179.427],"t":261,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.764,179.227],"t":262,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.504,179.065],"t":263,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.29,178.931],"t":264,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.11,178.819],"t":265,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.832,178.645],"t":267,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.555,178.472],"t":270,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.272,178.295],"t":278,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.264,178.29],"t":380,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.222,179.514],"t":381,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[293.538,183.461],"t":382,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[305.714,191.071],"t":383,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[327.48,204.675],"t":384,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[372.758,232.974],"t":385,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[424.317,265.198],"t":386,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[447.009,279.381],"t":387,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[460.167,287.605],"t":388,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[469.103,293.19],"t":389,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[475.697,297.31],"t":390,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[480.788,300.492],"t":391,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[484.853,303.033],"t":392,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[488.172,305.107],"t":393,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[490.906,306.816],"t":394,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[493.198,308.249],"t":395,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[495.121,309.451],"t":396,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[496.752,310.47],"t":397,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[498.133,311.333],"t":398,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[499.301,312.063],"t":399,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[500.29,312.681],"t":400,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[501.123,313.202],"t":401,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[501.814,313.634],"t":402,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.391,313.994],"t":403,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.861,314.288],"t":404,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[503.238,314.524],"t":405,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[503.53,314.706],"t":406,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"p":{"a":0,"k":[0,0]},"r":{"k":[{"s":[27.974],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.959],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.942],"t":146,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.922],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.898],"t":148,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.869],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.833],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.789],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.736],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.67],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.589],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.49],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.368],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.216],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.024],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.777],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.45],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.991],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.37],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.669],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.901],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.098],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.306],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[21.566],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[20.898],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[20.306],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.785],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.324],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.915],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.551],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.223],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.928],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.66],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.416],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.193],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.988],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.8],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.626],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.465],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.316],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.178],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.05],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.931],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.82],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.717],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.621],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.531],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.448],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.37],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.298],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.23],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.167],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.11],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.055],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.006],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.96],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.917],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.878],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.842],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.809],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.779],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.752],"t":205,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.728],"t":206,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.706],"t":207,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.687],"t":208,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.67],"t":209,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.655],"t":210,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.643],"t":211,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.633],"t":212,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.624],"t":213,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.61],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.603],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.579],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.532],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.45],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.278],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.082],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.996],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.946],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.912],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.887],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.868],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.853],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.84],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.83],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.821],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.808],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.794],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.78],"t":278,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.78],"t":380,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.907],"t":381,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.318],"t":382,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.109],"t":383,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.524],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.468],"t":385,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.82],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.295],"t":387,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.15],"t":388,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.731],"t":389,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.16],"t":390,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.491],"t":391,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.755],"t":392,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.971],"t":393,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.149],"t":394,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.298],"t":395,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.423],"t":396,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.529],"t":397,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.619],"t":398,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.694],"t":399,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.759],"t":400,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.813],"t":401,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.858],"t":402,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.895],"t":403,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.926],"t":404,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.95],"t":405,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.969],"t":406,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.993],"t":408,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"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":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Recents_LofiApp","parent":6,"tt":1,"tp":6,"refId":"comp_2","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":{"k":[{"s":[99.923,99.923,100],"t":144,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.828,99.828,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.768,99.768,100],"t":147,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.695,99.695,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.608,99.608,100],"t":149,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.501,99.501,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.37,99.37,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.211,99.211,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.014,99.014,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.773,98.773,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.478,98.478,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.112,98.112,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.658,97.658,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.085,97.085,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.348,96.348,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.371,95.371,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94,94,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.142,92.142,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.049,90.049,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.755,87.755,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.357,85.357,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.991,82.991,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.78,80.78,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[78.785,78.785,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[77.017,77.017,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[75.458,75.458,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[74.082,74.082,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[72.861,72.861,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[71.772,71.772,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70.794,70.794,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[69.911,69.911,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[69.111,69.111,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.382,68.382,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[67.715,67.715,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[67.104,67.104,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[66.542,66.542,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[66.022,66.022,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[65.542,65.542,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[65.098,65.098,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[64.685,64.685,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[64.302,64.302,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.947,63.947,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.615,63.615,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.306,63.306,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.02,63.02,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.751,62.751,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.503,62.503,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.271,62.271,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.055,62.055,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.853,61.853,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.665,61.665,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.492,61.492,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.331,61.331,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.182,61.182,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.044,61.044,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.917,60.917,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.801,60.801,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.693,60.693,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.595,60.595,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.505,60.505,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.424,60.424,100],"t":205,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.353,60.353,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.288,60.288,100],"t":207,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.229,60.229,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.18,60.18,100],"t":209,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.135,60.135,100],"t":210,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.098,60.098,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.043,60.043,100],"t":213,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60,60,100],"t":250,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.97,59.97,100],"t":251,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.871,59.871,100],"t":252,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.682,59.682,100],"t":253,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.344,59.344,100],"t":254,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[58.64,58.64,100],"t":255,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.839,57.839,100],"t":256,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.486,57.486,100],"t":257,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.281,57.281,100],"t":258,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.142,57.142,100],"t":259,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.04,57.04,100],"t":260,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.961,56.961,100],"t":261,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.898,56.898,100],"t":262,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.846,56.846,100],"t":263,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.804,56.804,100],"t":264,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.768,56.768,100],"t":265,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.713,56.713,100],"t":267,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.658,56.658,100],"t":270,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.602,56.602,100],"t":278,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.6,56.6,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.989,56.989,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[58.242,58.242,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.657,60.657,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[64.976,64.976,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[73.96,73.96,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.19,84.19,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.692,88.692,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.303,91.303,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.076,93.076,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.384,94.384,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.394,95.394,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.201,96.201,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.859,96.859,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.402,97.402,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.857,97.857,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.238,98.238,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.562,98.562,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.836,98.836,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.068,99.068,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.264,99.264,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.429,99.429,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.566,99.566,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.681,99.681,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.774,99.774,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.849,99.849,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.907,99.907,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"w":504,"h":315,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"t":277,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,-30.035,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[176.678,176.678,100]}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"second Tasks Zoom back","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":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":385,"s":[98,98,100]},{"t":410,"s":[95,95,100]}]}},"ao":0,"shapes":[],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Null :: Reposition Side Task","parent":9,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":250,"s":[-318.4,-38,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":255,"s":[-277.34,-48.1,0],"to":[0,0,0],"ti":[0,0,0]},{"t":280,"s":[-215.75,-63.25,0]}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":277,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,-111.72,0],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-111.197,0],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-109.514,0],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-106.268,0],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-100.462,0],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-88.39,0],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-74.643,0],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-68.591,0],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-65.083,0],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-62.7,0],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-60.943,0],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-59.584,0],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-58.5,0],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-57.616,0],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-56.886,0],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-56.276,0],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-55.762,0],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-55.328,0],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.96,0],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.648,0],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.385,0],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.163,0],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.977,0],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.824,0],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.698,0],"t":274,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.598,0],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.521,0],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.463,0],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.424,0],"t":278,"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":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":10,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.963},"t":217,"s":[-84.8,0,0],"to":[0,0,0],"ti":[0,0,0]},{"t":250,"s":[0,0,0]}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":250,"s":[302.4,189]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":255,"s":[227.56,142.34]},{"t":280,"s":[115.3,72.35]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":250,"s":[14.6]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":255,"s":[14.272]},{"t":280,"s":[13.78]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":277,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,-53.175,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.175,0],"t":510,"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":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":250,"s":[-411.95,20.325,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":255,"s":[-333.47,29.183,0],"to":[0,0,0],"ti":[0,0,0]},{"t":280,"s":[-215.75,42.47,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":[115.3,72.35]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":13.78},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Taskbar Lofi","fr":60,"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"app - 5","parent":9,"sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[51.5,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.652,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.136,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.013,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.449,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.806,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[61.3,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[66.437,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[68.94,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[70.432,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[71.462,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[72.229,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[72.83,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[73.314,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[73.714,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.048,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.334,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.578,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.789,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.971,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.131,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.269,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.389,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.493,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.584,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.663,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.731,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.789,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.839,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.915,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.982,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[76,0,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.779,0,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.066,0,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[73.709,0,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[71.271,0,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[66.2,0,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[60.425,0,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.886,0,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.41,0,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.409,0,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.67,0,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.1,0,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.646,0,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.275,0,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.968,0,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.711,0,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.495,0,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.312,0,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.157,0,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.026,0,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.916,0,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.822,0,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.745,0,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.68,0,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.628,0,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.585,0,0],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.552,0,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.501,0,0],"t":409,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[167,15,0]},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"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":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Ellipse 7511","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[167,15]},"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 - 5","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"app - 4","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[123.341,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.654,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.223,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[125.146,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[126.662,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[129.549,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[132.838,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[134.455,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[135.421,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[136.083,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[136.576,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[136.962,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.272,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.528,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.742,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.924,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.08,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.216,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.334,15,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.437,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.527,15,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.606,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.734,15,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.869,15,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.864,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.402,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.527,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[135.96,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[132.701,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[129.002,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[127.358,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[126.406,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[125.763,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[125.288,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.923,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.633,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.396,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.199,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.034,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.895,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.776,15,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.675,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.589,15,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.516,15,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.403,15,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.299,15,0],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[139,15,0]},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"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":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Ellipse 7508","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[139,15]},"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}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"app - 3","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[104.041,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.182,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.436,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.844,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.517,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.808,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.265,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.981,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.409,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.703,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.923,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.092,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.228,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.341,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.436,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.517,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.587,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.648,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.746,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.853,15,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.956,15,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.938,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.73,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.34,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.649,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.197,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.559,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.828,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.403,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.117,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.906,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.745,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.616,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.511,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.424,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.35,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.288,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.19,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.091,15,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[111,15,0]},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"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":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Ellipse 7507","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[111,15]},"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}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"app - 2","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[84.704,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.639,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.537,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.371,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.048,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.684,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.505,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.398,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.324,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.271,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.195,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.123,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.045,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.068,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.166,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.338,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.702,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.112,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.294,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.399,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.47,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.521,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.593,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.676,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[83,15,0]},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"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":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Ellipse 7506","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[83,15]},"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}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"app - 1","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[65.439,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.229,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.849,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.236,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[63.226,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[61.296,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[59.111,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[58.033,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.388,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.945,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.616,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.359,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.154,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.984,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.842,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.72,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.616,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.525,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.447,15,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.378,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.317,15,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.265,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.219,15,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.178,15,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.143,15,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.113,15,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.066,15,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.012,15,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55,15,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.092,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.403,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.986,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.027,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[59.212,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[61.67,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[62.762,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[63.396,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[63.825,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.141,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.385,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.578,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.736,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.867,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.977,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.07,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.149,15,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.217,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.274,15,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.323,15,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.364,15,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.426,15,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.491,15,0],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[55,15,0]},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"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":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Ellipse 7505","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[55,15]},"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":7,"ty":4,"nm":"divider","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":90},"p":{"k":[{"s":[51,15,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.913,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.615,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.073,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.194,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[47.751,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[45.001,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.869,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.328,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.409,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.778,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.309,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.941,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.645,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.402,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.199,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.025,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.876,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.747,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.635,15,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.536,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.451,15,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.376,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.31,15,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.253,15,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.203,15,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.161,15,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.124,15,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.093,15,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.068,15,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.047,15,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.017,15,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36,15,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.129,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.569,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.403,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.895,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.999,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[45.522,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[47.088,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[47.994,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[48.607,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.059,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.407,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.683,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.909,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.096,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.253,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.386,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.499,15,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.596,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.677,15,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.747,15,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.806,15,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.854,15,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.895,15,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.927,15,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.973,15,0],"t":406,"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":{"k":[{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2,-0.5],[2,-0.5]],"c":false}],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.019,-0.5],[2.019,-0.5]],"c":false}],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.077,-0.5],[2.077,-0.5]],"c":false}],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.185,-0.5],[2.185,-0.5]],"c":false}],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.361,-0.5],[2.361,-0.5]],"c":false}],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.65,-0.5],[2.65,-0.5]],"c":false}],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.2,-0.5],[3.2,-0.5]],"c":false}],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.829,-0.5],[3.829,-0.5]],"c":false}],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.136,-0.5],[4.136,-0.5]],"c":false}],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.318,-0.5],[4.318,-0.5]],"c":false}],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.444,-0.5],[4.444,-0.5]],"c":false}],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.538,-0.5],[4.538,-0.5]],"c":false}],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.612,-0.5],[4.612,-0.5]],"c":false}],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.671,-0.5],[4.671,-0.5]],"c":false}],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.72,-0.5],[4.72,-0.5]],"c":false}],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.761,-0.5],[4.761,-0.5]],"c":false}],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.796,-0.5],[4.796,-0.5]],"c":false}],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.826,-0.5],[4.826,-0.5]],"c":false}],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.852,-0.5],[4.852,-0.5]],"c":false}],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.874,-0.5],[4.874,-0.5]],"c":false}],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.894,-0.5],[4.894,-0.5]],"c":false}],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.91,-0.5],[4.91,-0.5]],"c":false}],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.925,-0.5],[4.925,-0.5]],"c":false}],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.938,-0.5],[4.938,-0.5]],"c":false}],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.949,-0.5],[4.949,-0.5]],"c":false}],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.959,-0.5],[4.959,-0.5]],"c":false}],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.967,-0.5],[4.967,-0.5]],"c":false}],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.974,-0.5],[4.974,-0.5]],"c":false}],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.98,-0.5],[4.98,-0.5]],"c":false}],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.99,-0.5],[4.99,-0.5]],"c":false}],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.996,-0.5],[4.996,-0.5]],"c":false}],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-5,-0.5],[5,-0.5]],"c":false}],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.973,-0.5],[4.973,-0.5]],"c":false}],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.887,-0.5],[4.887,-0.5]],"c":false}],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.72,-0.5],[4.72,-0.5]],"c":false}],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.421,-0.5],[4.421,-0.5]],"c":false}],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.8,-0.5],[3.8,-0.5]],"c":false}],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.093,-0.5],[3.093,-0.5]],"c":false}],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.782,-0.5],[2.782,-0.5]],"c":false}],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.601,-0.5],[2.601,-0.5]],"c":false}],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.479,-0.5],[2.479,-0.5]],"c":false}],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.388,-0.5],[2.388,-0.5]],"c":false}],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.318,-0.5],[2.318,-0.5]],"c":false}],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.263,-0.5],[2.263,-0.5]],"c":false}],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.217,-0.5],[2.217,-0.5]],"c":false}],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.18,-0.5],[2.18,-0.5]],"c":false}],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.148,-0.5],[2.148,-0.5]],"c":false}],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.122,-0.5],[2.122,-0.5]],"c":false}],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.099,-0.5],[2.099,-0.5]],"c":false}],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.081,-0.5],[2.081,-0.5]],"c":false}],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.064,-0.5],[2.064,-0.5]],"c":false}],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.051,-0.5],[2.051,-0.5]],"c":false}],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.039,-0.5],[2.039,-0.5]],"c":false}],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.03,-0.5],[2.03,-0.5]],"c":false}],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.022,-0.5],[2.022,-0.5]],"c":false}],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.01,-0.5],[2.01,-0.5]],"c":false}],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.006,-0.5],[2.006,-0.5]],"c":false}],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.004,-0.5],[2.004,-0.5]],"c":false}],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.001,-0.5],[2.001,-0.5]],"c":false}],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":1,"ml":4,"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":"divider","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":9,"sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[-52.349,0.652,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.453,0.652,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.813,0.652,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.464,0.652,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.515,0.652,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.247,0.652,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-59.565,0.652,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-63.323,0.652,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-65.162,0.652,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-66.258,0.652,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-67.015,0.652,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-67.578,0.652,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.019,0.652,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.375,0.652,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.668,0.652,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.914,0.652,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.122,0.652,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.301,0.652,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.456,0.652,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.59,0.652,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.708,0.652,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.81,0.652,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.9,0.652,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.978,0.652,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.047,0.652,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.106,0.652,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.157,0.652,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.2,0.652,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.268,0.652,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.328,0.652,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.349,0.652,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.193,0.652,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.662,0.652,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.662,0.652,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-66.874,0.652,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-63.132,0.652,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.906,0.652,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.04,0.652,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.956,0.652,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.22,0.652,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.678,0.652,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.259,0.652,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.925,0.652,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.654,0.652,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.43,0.652,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.241,0.652,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.082,0.652,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.947,0.652,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.831,0.652,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.734,0.652,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.65,0.652,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.58,0.652,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.522,0.652,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.474,0.652,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.435,0.652,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.404,0.652,0],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.354,0.652,0],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[6.826,6.826,0]},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"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":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Ellipse 12","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[2.5,9.501]},"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":"Ellipse 12","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Ellipse 11","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[2.5,2.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":"Ellipse 11","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Ellipse 5","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[9.5,2.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":"Ellipse 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.185,0.148],[0,0],[0,0],[0,0],[-0.086,0.24],[0,0.271],[0.468,0.462],[0.671,0],[0.468,-0.468],[0,-0.671],[-0.462,-0.468],[-0.671,0],[-0.24,0.086]],"o":[[0,0],[0,0],[0,0],[0.148,-0.185],[0.086,-0.24],[0,-0.671],[-0.462,-0.468],[-0.671,0],[-0.462,0.462],[0,0.671],[0.468,0.462],[0.271,0],[0.24,-0.086]],"v":[[0.48,0.998],[2.809,3.326],[3.326,2.809],[0.998,0.48],[1.349,-0.157],[1.478,-0.924],[0.776,-2.624],[-0.924,-3.326],[-2.633,-2.624],[-3.326,-0.924],[-2.633,0.785],[-0.924,1.478],[-0.157,1.349]],"c":true}},"nm":"Path 1","hd":false},{"ty":"mm","mm":5,"nm":"Merge Paths 1","hd":false},{"ind":2,"ty":"sh","ks":{"a":0,"k":{"i":[[0.326,-0.326],[0.462,0],[0.326,0.32],[0,0.462],[-0.32,0.32],[-0.462,0],[-0.32,-0.326],[0,-0.462]],"o":[[-0.32,0.32],[-0.462,0],[-0.32,-0.326],[0,-0.462],[0.326,-0.326],[0.462,0],[0.326,0.32],[0,0.462]],"v":[[0.249,0.259],[-0.924,0.739],[-2.106,0.259],[-2.587,-0.924],[-2.106,-2.097],[-0.924,-2.587],[0.249,-2.097],[0.739,-0.924]],"c":true}},"nm":"Path 2","hd":false},{"ty":"mm","mm":5,"nm":"Merge Paths 2","hd":false},{"ty":"st","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0.4},"lc":1,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"icon","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[10.326,10.326]},"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":"icon","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[91,15,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":{"k":[{"s":[120,4],"t":155,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.383,4.161],"t":156,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.592,4.668],"t":157,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[123.818,5.601],"t":158,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[127.463,7.13],"t":159,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[133.427,9.631],"t":160,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[144.8,14.4],"t":161,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[157.801,19.852],"t":162,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[164.141,22.511],"t":163,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[167.915,24.093],"t":164,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[170.516,25.184],"t":165,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[172.458,25.999],"t":166,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[173.978,26.636],"t":167,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[175.203,27.15],"t":168,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[176.216,27.574],"t":169,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[177.065,27.931],"t":170,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[177.788,28.234],"t":171,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[178.406,28.493],"t":172,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[178.938,28.716],"t":173,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[179.399,28.909],"t":174,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[179.8,29.078],"t":175,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.149,29.224],"t":176,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.454,29.352],"t":177,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.718,29.463],"t":178,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.949,29.559],"t":179,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.148,29.643],"t":180,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.32,29.715],"t":181,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.467,29.777],"t":182,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.592,29.829],"t":183,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.697,29.873],"t":184,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.784,29.91],"t":185,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.855,29.939],"t":186,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.909,29.962],"t":187,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.978,29.991],"t":189,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[182,30],"t":380,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.445,29.767],"t":381,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[179.655,29.017],"t":382,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[176.204,27.569],"t":383,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[170.034,24.982],"t":384,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[157.2,19.6],"t":385,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[142.586,13.472],"t":386,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[136.154,10.774],"t":387,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[132.424,9.21],"t":388,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[129.891,8.148],"t":389,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[128.022,7.364],"t":390,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[126.579,6.759],"t":391,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[125.427,6.276],"t":392,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[124.487,5.881],"t":393,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[123.712,5.556],"t":394,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[123.062,5.284],"t":395,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[122.517,5.055],"t":396,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[122.055,4.862],"t":397,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.663,4.697],"t":398,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.332,4.559],"t":399,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.051,4.441],"t":400,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.815,4.342],"t":401,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.62,4.26],"t":402,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.456,4.191],"t":403,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.323,4.135],"t":404,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.216,4.091],"t":405,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.133,4.056],"t":406,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.073,4.03],"t":407,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.03,4.013],"t":408,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.008,4.003],"t":409,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":32.672},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,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":"Taskbar Lofi","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Recents_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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":".onSecondaryFixed","cl":"onSecondaryFixed","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.145098039216,0.101960784314,0.01568627451,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":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82.5,140.5,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.145098039216,0.101960784314,0.01568627451,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.145098039216,0.101960784314,0.01568627451,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.145098039216,0.101960784314,0.01568627451,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.145098039216,0.101960784314,0.01568627451,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.145098039216,0.101960784314,0.01568627451,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.145098039216,0.101960784314,0.01568627451,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.145098039216,0.101960784314,0.01568627451,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.145098039216,0.101960784314,0.01568627451,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":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.325490196078,0.270588235294,0.164705882353,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}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":2,"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":250,"s":[100]},{"t":256,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,29.984,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.965,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.936,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.894,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.84,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.77,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.682,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.574,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.445,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.294,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.121,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.925,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.746,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.548,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.33,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.092,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.832,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.548,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.239,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.903,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.536,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.14,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,25.709,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,25.241,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,24.73,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,24.171,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,23.563,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,22.898,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,22.171,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,21.373,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,20.496,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,19.524,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,18.451,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,17.263,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,15.943,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,14.475,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,12.841,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,11.018,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,9.023,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,6.87,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,4.614,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,2.333,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0.106,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-1.975,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-3.877,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-5.591,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-7.125,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-8.492,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-9.714,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-10.799,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-11.771,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-12.643,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-13.428,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-14.138,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-14.777,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-15.355,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-15.879,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-16.354,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-16.784,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.177,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.532,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.854,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.146,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.409,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.645,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.858,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.048,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.217,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.366,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.496,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.61,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.707,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.788,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.856,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.911,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.954,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.984,0],"t":203,"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":248,"s":[28,28]},{"t":258,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,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":248,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":258,"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":248,"s":[28,28]},{"t":258,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,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":248,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":258,"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":248,"s":[28,28]},{"t":258,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,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":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.32549020648,0.270588248968,0.164705887437,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":600,"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":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"Recents_EDU Loop","parent":4,"tt":1,"tp":4,"refId":"comp_0","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":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":50},"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},{"ddd":0,"ind":8,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","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":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,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},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","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.850980392157,0.76862745098,0.627450980392,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":511,"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.850980392157,0.76862745098,0.627450980392,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":600,"st":0,"ct":1,"bm":0}],"markers":[{"tm":121,"cm":"start","dr":0},{"tm":142,"cm":"gesture","dr":75},{"tm":250,"cm":"release","dr":36},{"tm":356,"cm":"FLIP","dr":0},{"tm":392,"cm":"launch","dr":66}],"props":{}}
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/trackpad_recent_apps_success.json b/packages/SystemUI/res/raw/trackpad_recent_apps_success.json new file mode 100644 index 000000000000..bec6f353f380 --- /dev/null +++ b/packages/SystemUI/res/raw/trackpad_recent_apps_success.json @@ -0,0 +1 @@ +{"v":"5.12.1","fr":60,"ip":0,"op":50,"w":554,"h":564,"nm":"Trackpad-JSON_Recents-Success","ddd":0,"assets":[{"id":"comp_0","nm":"TrackpadAK_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":".secondaryFixedDim","cl":"secondaryFixedDim","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.850980392157,0.76862745098,0.627450980392,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":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,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":"Checkbox - Widget","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Recents_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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":".onSecondaryFixed","cl":"onSecondaryFixed","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.145098039216,0.101960784314,0.01568627451,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":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82.5,140.5,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.145098039216,0.101960784314,0.01568627451,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.145098039216,0.101960784314,0.01568627451,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.145098039216,0.101960784314,0.01568627451,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.145098039216,0.101960784314,0.01568627451,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.145098039216,0.101960784314,0.01568627451,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.145098039216,0.101960784314,0.01568627451,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.145098039216,0.101960784314,0.01568627451,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.145098039216,0.101960784314,0.01568627451,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":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.325490196078,0.270588235294,0.164705882353,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}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"TrackpadAK_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":2,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","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":[277,197.5,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":2}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"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.145098039216,0.101960784314,0.01568627451,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":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.32549020648,0.270588248968,0.164705887437,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":600,"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":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"Recents_LofiApp","tt":1,"tp":4,"refId":"comp_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":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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,"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":2}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"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.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":50},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","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":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,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},{"ddd":0,"ind":8,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","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.850980392157,0.76862745098,0.627450980392,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.850980392157,0.76862745098,0.627450980392,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/dimens.xml b/packages/SystemUI/res/values/dimens.xml index e1808fa7532d..e94248dc72ce 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1976,6 +1976,14 @@ <dimen name="backlight_indicator_step_small_radius">4dp</dimen> <dimen name="backlight_indicator_step_large_radius">28dp</dimen> + <!-- Touchpad gestures tutorial--> + <!-- This value is in unit of dp/ms + TriggerSwipeUpTouchTracker (which is base for gesture tutorial implementation) uses value + of 0.5dp but from manual testing it's too high and doesn't really feel like it's forcing + slowing down. Also for tutorial it should be fine to lean to the side of being more strict + rather than not strict enough and not teaching user the proper gesture as a result.--> + <dimen name="touchpad_recent_apps_gesture_velocity_threshold">0.05dp</dimen> + <!-- Broadcast dialog --> <dimen name="broadcast_dialog_title_img_margin_top">18dp</dimen> <dimen name="broadcast_dialog_title_text_size">24sp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index a41da6c492ca..f9c2aef5f070 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3729,8 +3729,8 @@ <string name="touchpad_tutorial_back_gesture_button">Back gesture</string> <!-- Label for button opening tutorial for back gesture on touchpad [CHAR LIMIT=NONE] --> <string name="touchpad_tutorial_home_gesture_button">Home gesture</string> - <!-- Label for button opening tutorial on using Action key from keyboard [CHAR LIMIT=NONE] --> - <string name="touchpad_tutorial_action_key_button">Action key</string> + <!-- Label for button opening tutorial for "view recent apps" gesture on touchpad [CHAR LIMIT=NONE] --> + <string name="touchpad_tutorial_recent_apps_gesture_button">View recent apps</string> <!-- Label for button finishing touchpad tutorial [CHAR LIMIT=NONE] --> <string name="touchpad_tutorial_done_button">Done</string> <!-- BACK GESTURE --> @@ -3752,6 +3752,15 @@ Action + ESC for this.</string> <string name="touchpad_home_gesture_success_title">Nice!</string> <!-- Text shown to the user after they complete home gesture tutorial [CHAR LIMIT=NONE] --> <string name="touchpad_home_gesture_success_body">You completed the go home gesture.</string> + <!-- RECENT APPS GESTURE --> + <!-- Touchpad recent apps gesture action name in tutorial [CHAR LIMIT=NONE] --> + <string name="touchpad_recent_apps_gesture_action_title">View recent apps</string> + <!-- Touchpad recent apps gesture guidance in gestures tutorial [CHAR LIMIT=NONE] --> + <string name="touchpad_recent_apps_gesture_guidance">Swipe up and hold using three fingers on your touchpad.</string> + <!-- Screen title after recent apps gesture was done successfully [CHAR LIMIT=NONE] --> + <string name="touchpad_recent_apps_gesture_success_title">Great job!</string> + <!-- Text shown to the user after they complete recent apps gesture tutorial [CHAR LIMIT=NONE] --> + <string name="touchpad_recent_apps_gesture_success_body">You completed the view recent apps gesture.</string> <!-- KEYBOARD TUTORIAL--> <!-- Action key tutorial title [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt index 0e39a99765bd..ec0322736f4b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt @@ -21,8 +21,10 @@ import android.os.Bundle import android.util.SizeF import com.android.app.tracing.coroutines.withContext import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.communal.widgets.CommunalAppWidgetHostView +import com.android.systemui.communal.widgets.WidgetInteractionHandler import com.android.systemui.dagger.qualifiers.UiBackground import javax.inject.Inject import kotlin.coroutines.CoroutineContext @@ -33,6 +35,8 @@ class WidgetViewFactory constructor( @UiBackground private val uiBgContext: CoroutineContext, private val appWidgetHost: CommunalAppWidgetHost, + private val interactionHandler: WidgetInteractionHandler, + private val listenerFactory: AppWidgetHostListenerDelegate.Factory, ) { suspend fun createWidget( context: Context, @@ -40,18 +44,20 @@ constructor( size: SizeF, ): CommunalAppWidgetHostView = withContext("$TAG#createWidget", uiBgContext) { - appWidgetHost - .createViewForCommunal(context, model.appWidgetId, model.providerInfo) - .apply { - updateAppWidgetSize( - /* newOptions = */ Bundle(), - /* minWidth = */ size.width.toInt(), - /* minHeight = */ size.height.toInt(), - /* maxWidth = */ size.width.toInt(), - /* maxHeight = */ size.height.toInt(), - /* ignorePadding = */ true, - ) - } + val view = CommunalAppWidgetHostView(context, interactionHandler) + view.setAppWidget(model.appWidgetId, model.providerInfo) + // Instead of setting the view as the listener directly, we wrap the view in a delegate + // which ensures the callbacks always get called on the main thread. + appWidgetHost.setListener(model.appWidgetId, listenerFactory.create(view)) + view.updateAppWidgetSize( + /* newOptions = */ Bundle(), + /* minWidth = */ size.width.toInt(), + /* minHeight = */ size.height.toInt(), + /* maxWidth = */ size.width.toInt(), + /* maxHeight = */ size.height.toInt(), + /* ignorePadding = */ true, + ) + view } private companion object { diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt new file mode 100644 index 000000000000..f3416216afdd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.widgets + +import android.appwidget.AppWidgetHost.AppWidgetHostListener +import android.appwidget.AppWidgetProviderInfo +import android.widget.RemoteViews +import com.android.systemui.dagger.qualifiers.Main +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.util.concurrent.Executor + +/** + * Wrapper for an [AppWidgetHostListener] to ensure the callbacks are executed on the main thread. + */ +class AppWidgetHostListenerDelegate +@AssistedInject +constructor( + @Main private val mainExecutor: Executor, + @Assisted private val listener: AppWidgetHostListener, +) : AppWidgetHostListener { + + @AssistedFactory + interface Factory { + fun create(listener: AppWidgetHostListener): AppWidgetHostListenerDelegate + } + + override fun onUpdateProviderInfo(appWidget: AppWidgetProviderInfo?) { + mainExecutor.execute { listener.onUpdateProviderInfo(appWidget) } + } + + override fun updateAppWidget(views: RemoteViews?) { + mainExecutor.execute { listener.updateAppWidget(views) } + } + + override fun onViewDataChanged(viewId: Int) { + mainExecutor.execute { listener.onViewDataChanged(viewId) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt index 10a565f49a8f..b46698ed87f2 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt @@ -17,11 +17,7 @@ package com.android.systemui.communal.widgets import android.appwidget.AppWidgetHost -import android.appwidget.AppWidgetHostView -import android.appwidget.AppWidgetProviderInfo import android.content.Context -import android.os.Looper -import android.widget.RemoteViews import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import javax.annotation.concurrent.GuardedBy @@ -36,11 +32,8 @@ class CommunalAppWidgetHost( context: Context, private val backgroundScope: CoroutineScope, hostId: Int, - private val interactionHandler: RemoteViews.InteractionHandler, - looper: Looper, logBuffer: LogBuffer, -) : AppWidgetHost(context, hostId, interactionHandler, looper) { - +) : AppWidgetHost(context, hostId) { private val logger = Logger(logBuffer, TAG) private val _appWidgetIdToRemove = MutableSharedFlow<Int>() @@ -50,29 +43,6 @@ class CommunalAppWidgetHost( @GuardedBy("observers") private val observers = mutableSetOf<Observer>() - override fun onCreateView( - context: Context, - appWidgetId: Int, - appWidget: AppWidgetProviderInfo? - ): AppWidgetHostView { - return CommunalAppWidgetHostView(context, interactionHandler) - } - - /** - * Creates and returns a [CommunalAppWidgetHostView]. This method does the same thing as - * `createView`. The only difference is that the returned value will be casted to - * [CommunalAppWidgetHostView]. - */ - fun createViewForCommunal( - context: Context?, - appWidgetId: Int, - appWidget: AppWidgetProviderInfo? - ): CommunalAppWidgetHostView { - // `createView` internally calls `onCreateView` to create the view. We cannot override - // `createView`, but we are sure that the hostView is `CommunalAppWidgetHostView` - return createView(context, appWidgetId, appWidget) as CommunalAppWidgetHostView - } - override fun onAppWidgetRemoved(appWidgetId: Int) { backgroundScope.launch { logger.i({ "App widget removed from system: $int1" }) { int1 = appWidgetId } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt index 684303ae73cc..f4962085000c 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt @@ -20,7 +20,6 @@ package com.android.systemui.communal.widgets import android.appwidget.AppWidgetManager import android.content.Context import android.content.res.Resources -import android.os.Looper import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -46,18 +45,9 @@ interface CommunalWidgetModule { fun provideCommunalAppWidgetHost( @Application context: Context, @Background backgroundScope: CoroutineScope, - interactionHandler: WidgetInteractionHandler, - @Main looper: Looper, @CommunalLog logBuffer: LogBuffer, ): CommunalAppWidgetHost { - return CommunalAppWidgetHost( - context, - backgroundScope, - APP_WIDGET_HOST_ID, - interactionHandler, - looper, - logBuffer, - ) + return CommunalAppWidgetHost(context, backgroundScope, APP_WIDGET_HOST_ID, logBuffer) } @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt index 7e13d2282e05..4b62eab08775 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt @@ -181,7 +181,7 @@ constructor( } .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), + started = SharingStarted.Eagerly, initialValue = KeyguardQuickAffordanceViewModel( slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId() @@ -202,7 +202,7 @@ constructor( } .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), + started = SharingStarted.Eagerly, initialValue = KeyguardQuickAffordanceViewModel( slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index eaa61a113ee6..38ca888eee38 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -36,6 +36,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED @@ -174,6 +175,9 @@ constructor( keyguardTransitionInteractor.isInTransition( Edge.create(from = LOCKSCREEN, to = DREAMING) ), + keyguardTransitionInteractor.isInTransition( + Edge.create(from = LOCKSCREEN, to = OCCLUDED) + ), ), isOnLockscreen, shadeInteractor.qsExpansion, diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index a402a9d5ae7c..ac49e91c777c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -101,10 +101,10 @@ import com.android.systemui.navigationbar.views.buttons.KeyButtonView; import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.SceneContainerFlag; -import com.android.systemui.scene.shared.model.SceneFamilies; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeViewController; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.QuickStepContract; @@ -158,6 +158,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final ScreenPinningRequest mScreenPinningRequest; private final NotificationShadeWindowController mStatusBarWinController; private final Provider<SceneInteractor> mSceneInteractor; + private final Provider<ShadeInteractor> mShadeInteractor; private final KeyboardTouchpadEduStatsInteractor mKeyboardTouchpadEduStatsInteractor; @@ -246,11 +247,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } else if (action == ACTION_UP) { // Gesture was too short to be picked up by scene container touch - // handling; programmatically start the transition to shade scene. - mSceneInteractor.get().changeScene( - SceneFamilies.NotifShade, - "short launcher swipe" - ); + // handling; programmatically start the transition to the shade. + mShadeInteractor.get().expandNotificationShade("short launcher swipe"); } } event.recycle(); @@ -267,10 +265,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mSceneInteractor.get().onRemoteUserInputStarted( "trackpad swipe"); } else if (action == ACTION_UP) { - mSceneInteractor.get().changeScene( - SceneFamilies.NotifShade, - "short trackpad swipe" - ); + mShadeInteractor.get().expandNotificationShade("short trackpad swipe"); } mStatusBarWinController.getWindowRootView().dispatchTouchEvent(event); } else { @@ -652,6 +647,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis NotificationShadeWindowController statusBarWinController, SysUiState sysUiState, Provider<SceneInteractor> sceneInteractor, + Provider<ShadeInteractor> shadeInteractor, UserTracker userTracker, UserManager userManager, WakefulnessLifecycle wakefulnessLifecycle, @@ -688,6 +684,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mScreenPinningRequest = screenPinningRequest; mStatusBarWinController = statusBarWinController; mSceneInteractor = sceneInteractor; + mShadeInteractor = shadeInteractor; mUserTracker = userTracker; mConnectionBackoffAttempts = 0; mRecentsComponentName = ComponentName.unflattenFromString(context.getString( diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index b11d8ed9ee3d..c5e0ccccb4d1 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -280,6 +280,8 @@ constructor( applicationScope.launch { sceneInteractor.currentScene.collectLatest { currentScene -> if (currentScene == Scenes.Lockscreen) { + // Wait for the screen to be on + powerInteractor.isAwake.first { it } // Wait for surface to become visible windowMgrLockscreenVisInteractor.surfaceBehindVisibility.first { it } // Make sure the device is actually unlocked before force-changing the scene diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java index d4e711e38b3c..663ba20070bd 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java @@ -41,7 +41,6 @@ import androidx.concurrent.futures.CallbackToFutureAdapter; import androidx.exifinterface.media.ExifInterface; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.flags.FeatureFlags; import com.google.common.util.concurrent.ListenableFuture; @@ -94,12 +93,10 @@ public class ImageExporter { private final ContentResolver mResolver; private CompressFormat mCompressFormat = CompressFormat.PNG; private int mQuality = 100; - private final FeatureFlags mFlags; @Inject - public ImageExporter(ContentResolver resolver, FeatureFlags flags) { + public ImageExporter(ContentResolver resolver) { mResolver = resolver; - mFlags = flags; } /** @@ -161,8 +158,7 @@ public class ImageExporter { ZonedDateTime captureTime = ZonedDateTime.now(ZoneId.systemDefault()); return export(executor, new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat, - mQuality, /* publish */ true, owner, mFlags, - createFilename(captureTime, mCompressFormat, displayId))); + mQuality, owner, createFilename(captureTime, mCompressFormat, displayId))); } /** @@ -184,7 +180,8 @@ public class ImageExporter { bitmap, ZonedDateTime.now(ZoneId.systemDefault()), format, - mQuality, /* publish */ true, owner, mFlags, + mQuality, + owner, createSystemFileDisplayName(fileName, format), true /* allowOverwrite */)); } @@ -199,8 +196,7 @@ public class ImageExporter { public ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime, UserHandle owner, int displayId) { return export(executor, new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat, - mQuality, /* publish */ true, owner, mFlags, - createFilename(captureTime, mCompressFormat, displayId))); + mQuality, owner, createFilename(captureTime, mCompressFormat, displayId))); } /** @@ -213,8 +209,7 @@ public class ImageExporter { ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime, UserHandle owner, String fileName) { return export(executor, new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat, - mQuality, /* publish */ true, owner, mFlags, - createSystemFileDisplayName(fileName, mCompressFormat))); + mQuality, owner, createSystemFileDisplayName(fileName, mCompressFormat))); } /** @@ -249,7 +244,6 @@ public class ImageExporter { public String fileName; public long timestamp; public CompressFormat format; - public boolean published; @Override public String toString() { @@ -259,7 +253,6 @@ public class ImageExporter { sb.append(", fileName='").append(fileName).append('\''); sb.append(", timestamp=").append(timestamp); sb.append(", format=").append(format); - sb.append(", published=").append(published); sb.append('}'); return sb.toString(); } @@ -274,8 +267,6 @@ public class ImageExporter { private final int mQuality; private final UserHandle mOwner; private final String mFileName; - private final boolean mPublish; - private final FeatureFlags mFlags; /** * This variable specifies the behavior when a file to be exported has a same name and @@ -287,15 +278,14 @@ public class ImageExporter { private final boolean mAllowOverwrite; Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime, - CompressFormat format, int quality, boolean publish, UserHandle owner, - FeatureFlags flags, String fileName) { - this(resolver, requestId, bitmap, captureTime, format, quality, publish, owner, flags, - fileName, false /* allowOverwrite */); + CompressFormat format, int quality, UserHandle owner, String fileName) { + this(resolver, requestId, bitmap, captureTime, format, quality, owner, fileName, + false /* allowOverwrite */); } Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime, - CompressFormat format, int quality, boolean publish, UserHandle owner, - FeatureFlags flags, String fileName, boolean allowOverwrite) { + CompressFormat format, int quality, UserHandle owner, + String fileName, boolean allowOverwrite) { mResolver = resolver; mRequestId = requestId; mBitmap = bitmap; @@ -304,8 +294,6 @@ public class ImageExporter { mQuality = quality; mOwner = owner; mFileName = fileName; - mPublish = publish; - mFlags = flags; mAllowOverwrite = allowOverwrite; } @@ -320,7 +308,7 @@ public class ImageExporter { start = Instant.now(); } - uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner, mFlags, + uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner, mAllowOverwrite); throwIfInterrupted(); @@ -332,10 +320,7 @@ public class ImageExporter { writeExif(mResolver, uri, mRequestId, width, height, mCaptureTime); throwIfInterrupted(); - if (mPublish) { - publishEntry(mResolver, uri); - result.published = true; - } + publishEntry(mResolver, uri); result.timestamp = mCaptureTime.toInstant().toEpochMilli(); result.requestId = mRequestId; @@ -365,7 +350,7 @@ public class ImageExporter { } private static Uri createEntry(ContentResolver resolver, CompressFormat format, - ZonedDateTime time, String fileName, UserHandle owner, FeatureFlags flags, + ZonedDateTime time, String fileName, UserHandle owner, boolean allowOverwrite) throws ImageExportException { Trace.beginSection("ImageExporter_createEntry"); try { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 31813b240c37..42499f043457 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -207,7 +207,7 @@ import com.android.systemui.statusbar.phone.BounceInterpolator; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; import com.android.systemui.statusbar.phone.KeyguardBypassController; diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt index 5d03a28e7f09..361226a4df18 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt @@ -91,17 +91,14 @@ constructor( } override fun instantCollapseShade() { - sceneInteractor.snapToScene( - SceneFamilies.Home, - "hide shade", - ) + sceneInteractor.snapToScene(SceneFamilies.Home, "hide shade") } override fun animateCollapseShade( flags: Int, force: Boolean, delayed: Boolean, - speedUpFactor: Float + speedUpFactor: Float, ) { if (!force && !shadeInteractor.isAnyExpanded.value) { runPostCollapseActions() @@ -147,7 +144,7 @@ constructor( if (shadeInteractor.isAnyExpanded.value) { commandQueue.animateCollapsePanels( CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */ + true, /* force */ ) assistManagerLazy.get().hideAssist() } @@ -172,17 +169,11 @@ constructor( } override fun expandToNotifications() { - sceneInteractor.changeScene( - SceneFamilies.NotifShade, - "ShadeController.animateExpandShade", - ) + shadeInteractor.expandNotificationShade("ShadeController.animateExpandShade") } override fun expandToQs() { - sceneInteractor.changeScene( - SceneFamilies.QuickSettings, - "ShadeController.animateExpandQs", - ) + shadeInteractor.expandQuickSettingsShade("ShadeController.animateExpandQs") } override fun setVisibilityListener(listener: ShadeVisibilityListener) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index c49cfbde25a5..cb589aa10cd9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -194,6 +194,7 @@ constructor( if (qsVisible && field != value) { header.alpha = ShadeInterpolation.getContentAlpha(value) field = value + updateIgnoredSlots() } } @@ -538,7 +539,7 @@ constructor( private fun updateIgnoredSlots() { // switching from QQS to QS state halfway through the transition - if (singleCarrier || qsExpandedFraction < 0.5) { + if (singleCarrier || (!largeScreenActive && qsExpandedFraction < 0.5)) { iconContainer.removeIgnoredSlots(carrierIconSlots) } else { iconContainer.addIgnoredSlots(carrierIconSlots) diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index 6fb96da2c186..b046c50b05d3 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -140,6 +140,18 @@ interface BaseShadeInteractor { * animating. */ val isUserInteractingWithQs: Flow<Boolean> + + /** + * Triggers the expansion (opening) of the notification shade. If the notification shade is + * already open, this has no effect. + */ + fun expandNotificationShade(loggingReason: String) + + /** + * Triggers the expansion (opening) of the quick settings shade. If the quick settings shade is + * already open, this has no effect. + */ + fun expandQuickSettingsShade(loggingReason: String) } fun createAnyExpansionFlow( diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt index 6c0b55a5dd57..fb1482890b87 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt @@ -49,4 +49,8 @@ class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor { override val isShadeLayoutWide: StateFlow<Boolean> = inactiveFlowBoolean override fun getTopEdgeSplitFraction(): Float = 0.5f + + override fun expandNotificationShade(loggingReason: String) {} + + override fun expandQuickSettingsShade(loggingReason: String) {} } 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 f48e31e1d7eb..df094864a71b 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 @@ -61,7 +61,7 @@ constructor( keyguardRepository.statusBarState, repository.legacyShadeExpansion, repository.qsExpansion, - sharedNotificationContainerInteractor.isSplitShadeEnabled + sharedNotificationContainerInteractor.isSplitShadeEnabled, ) { lockscreenShadeExpansion, statusBarState, @@ -97,13 +97,13 @@ constructor( repository.legacyExpandedOrAwaitingInputTransfer.stateIn( scope, SharingStarted.Eagerly, - false + false, ) override val isUserInteractingWithShade: Flow<Boolean> = combine( userInteractingFlow(repository.legacyShadeTracking, repository.legacyShadeExpansion), - repository.legacyLockscreenShadeTracking + repository.legacyLockscreenShadeTracking, ) { legacyShadeTracking, legacyLockscreenShadeTracking -> legacyShadeTracking || legacyLockscreenShadeTracking } @@ -111,6 +111,18 @@ constructor( override val isUserInteractingWithQs: Flow<Boolean> = userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion) + override fun expandNotificationShade(loggingReason: String) { + throw UnsupportedOperationException( + "expandNotificationShade() is not supported in legacy shade" + ) + } + + override fun expandQuickSettingsShade(loggingReason: String) { + throw UnsupportedOperationException( + "expandQuickSettingsShade() is not supported in legacy shade" + ) + } + /** * Return a flow for whether a user is interacting with an expandable shade component using * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that @@ -118,7 +130,7 @@ constructor( */ private fun userInteractingFlow( tracking: Flow<Boolean>, - expansion: StateFlow<Float> + expansion: StateFlow<Float>, ): Flow<Boolean> { return flow { // initial value is false 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 e84cfa51dd67..81bf712f21e5 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 @@ -17,81 +17,70 @@ package com.android.systemui.shade.domain.interactor import com.android.app.tracing.FlowTracing.traceAsCounter +import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.OverlayKey import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag -import com.android.systemui.scene.shared.model.SceneFamilies -import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.scene.shared.model.Overlays +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** ShadeInteractor implementation for Scene Container. */ +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class ShadeInteractorSceneContainerImpl @Inject constructor( @Application scope: CoroutineScope, - sceneInteractor: SceneInteractor, - shadeRepository: ShadeRepository, + private val sceneInteractor: SceneInteractor, + private val shadeModeInteractor: ShadeModeInteractor, ) : BaseShadeInteractor { init { SceneContainerFlag.assertInNewMode() } override val shadeExpansion: StateFlow<Float> = - sceneBasedExpansion(sceneInteractor, SceneFamilies.NotifShade) + shadeModeInteractor.shadeMode + .flatMapLatest { shadeMode -> + transitionProgressExpansion(shadeMode.notificationsContentKey) + } .traceAsCounter("panel_expansion") { (it * 100f).toInt() } .stateIn(scope, SharingStarted.Eagerly, 0f) - private val sceneBasedQsExpansion = - sceneBasedExpansion(sceneInteractor, SceneFamilies.QuickSettings) - override val qsExpansion: StateFlow<Float> = - combine( - shadeRepository.isShadeLayoutWide, - shadeExpansion, - sceneBasedQsExpansion, - ) { isSplitShadeEnabled, shadeExpansion, qsExpansion -> - if (isSplitShadeEnabled) { - shadeExpansion - } else { - qsExpansion - } - } + shadeModeInteractor.shadeMode + .flatMapLatest { shadeMode -> transitionProgressExpansion(shadeMode.qsContentKey) } .stateIn(scope, SharingStarted.Eagerly, 0f) override val isQsExpanded: StateFlow<Boolean> = - qsExpansion - .map { it > 0 } - .distinctUntilChanged() - .stateIn(scope, SharingStarted.Eagerly, false) + qsExpansion.map { it > 0 }.stateIn(scope, SharingStarted.Eagerly, false) override val isQsBypassingShade: Flow<Boolean> = - combine( - sceneInteractor.resolveSceneFamily(SceneFamilies.QuickSettings), - sceneInteractor.resolveSceneFamily(SceneFamilies.NotifShade), - ::Pair - ) - .flatMapLatestConflated { (quickSettingsScene, notificationsScene) -> + shadeModeInteractor.shadeMode + .flatMapLatestConflated { shadeMode -> sceneInteractor.transitionState .map { state -> when (state) { is ObservableTransitionState.Idle -> false is ObservableTransitionState.Transition -> - state.toContent == quickSettingsScene && - state.fromContent != notificationsScene + state.toContent == shadeMode.qsContentKey && + state.fromContent != shadeMode.notificationsContentKey } } .distinctUntilChanged() @@ -99,21 +88,22 @@ constructor( .distinctUntilChanged() override val isQsFullscreen: Flow<Boolean> = - combine( - shadeRepository.isShadeLayoutWide, - sceneInteractor.resolveSceneFamily(SceneFamilies.QuickSettings), - ::Pair - ) - .flatMapLatestConflated { (isShadeLayoutWide, quickSettingsScene) -> - sceneInteractor.transitionState - .map { state -> - when (state) { - is ObservableTransitionState.Idle -> - !isShadeLayoutWide && state.currentScene == quickSettingsScene - is ObservableTransitionState.Transition -> false - } - } - .distinctUntilChanged() + shadeModeInteractor.shadeMode + .flatMapLatest { shadeMode -> + when (shadeMode) { + ShadeMode.Single -> + sceneInteractor.transitionState + .map { state -> + when (state) { + is ObservableTransitionState.Idle -> + state.currentScene == Scenes.QuickSettings + is ObservableTransitionState.Transition -> false + } + } + .distinctUntilChanged() + ShadeMode.Split, + ShadeMode.Dual -> flowOf(false) + } } .distinctUntilChanged() @@ -121,16 +111,79 @@ constructor( createAnyExpansionFlow(scope, shadeExpansion, qsExpansion) override val isAnyExpanded = - anyExpansion - .map { it > 0f } - .distinctUntilChanged() - .stateIn(scope, SharingStarted.Eagerly, false) + anyExpansion.map { it > 0f }.stateIn(scope, SharingStarted.Eagerly, false) override val isUserInteractingWithShade: Flow<Boolean> = - sceneBasedInteracting(sceneInteractor, SceneFamilies.NotifShade) + shadeModeInteractor.shadeMode.flatMapLatest { shadeMode -> + when (shadeMode) { + ShadeMode.Single, + ShadeMode.Split -> sceneBasedInteracting(sceneInteractor, Scenes.Shade) + ShadeMode.Dual -> + overlayBasedInteracting(sceneInteractor, Overlays.NotificationsShade) + } + } override val isUserInteractingWithQs: Flow<Boolean> = - sceneBasedInteracting(sceneInteractor, SceneFamilies.QuickSettings) + shadeModeInteractor.shadeMode.flatMapLatest { shadeMode -> + when (shadeMode) { + ShadeMode.Single -> sceneBasedInteracting(sceneInteractor, Scenes.QuickSettings) + ShadeMode.Split -> sceneBasedInteracting(sceneInteractor, Scenes.Shade) + ShadeMode.Dual -> + overlayBasedInteracting(sceneInteractor, Overlays.QuickSettingsShade) + } + } + + override fun expandNotificationShade(loggingReason: String) { + if (shadeModeInteractor.isDualShade) { + if (Overlays.QuickSettingsShade in sceneInteractor.currentOverlays.value) { + sceneInteractor.replaceOverlay( + from = Overlays.QuickSettingsShade, + to = Overlays.NotificationsShade, + loggingReason = loggingReason, + ) + } else { + sceneInteractor.showOverlay( + overlay = Overlays.NotificationsShade, + loggingReason = loggingReason, + ) + } + } else { + sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = loggingReason) + } + } + + override fun expandQuickSettingsShade(loggingReason: String) { + if (shadeModeInteractor.isDualShade) { + if (Overlays.NotificationsShade in sceneInteractor.currentOverlays.value) { + sceneInteractor.replaceOverlay( + from = Overlays.NotificationsShade, + to = Overlays.QuickSettingsShade, + loggingReason = loggingReason, + ) + } else { + sceneInteractor.showOverlay( + overlay = Overlays.QuickSettingsShade, + loggingReason = loggingReason, + ) + } + } else { + sceneInteractor.changeScene( + toScene = Scenes.QuickSettings, + loggingReason = loggingReason, + ) + } + } + + /** + * Returns a flow that uses scene transition progress to and from a content to a 0-1 expansion + * amount float. + */ + private fun transitionProgressExpansion(contentKey: ContentKey): Flow<Float> { + return when (contentKey) { + is SceneKey -> sceneBasedExpansion(sceneInteractor, contentKey) + is OverlayKey -> overlayBasedExpansion(sceneInteractor, contentKey) + } + } /** * Returns a flow that uses scene transition progress to and from a scene that is pulled down @@ -181,4 +234,60 @@ constructor( } } .distinctUntilChanged() + + /** + * Returns a flow that uses scene transition progress to and from [overlay] to a 0-1 expansion + * amount float. + */ + private fun overlayBasedExpansion(sceneInteractor: SceneInteractor, overlay: OverlayKey) = + sceneInteractor.transitionState + .flatMapLatestConflated { state -> + when (state) { + is ObservableTransitionState.Idle -> + flowOf(if (overlay in state.currentOverlays) 1f else 0f) + is ObservableTransitionState.Transition -> + if (state.toContent == overlay) { + state.progress + } else if (state.fromContent == overlay) { + state.progress.map { progress -> 1 - progress } + } else { + flowOf(0f) + } + } + } + .distinctUntilChanged() + + /** + * Returns a flow that uses scene transition data to determine whether the user is interacting + * with [overlay]. + */ + private fun overlayBasedInteracting(sceneInteractor: SceneInteractor, overlay: OverlayKey) = + sceneInteractor.transitionState + .map { state -> + when (state) { + is ObservableTransitionState.Idle -> false + is ObservableTransitionState.Transition -> + state.isInitiatedByUserInput && + (state.toContent == overlay || state.fromContent == overlay) + } + } + .distinctUntilChanged() + + private val ShadeMode.notificationsContentKey: ContentKey + get() { + return when (this) { + ShadeMode.Single, + ShadeMode.Split -> Scenes.Shade + ShadeMode.Dual -> Overlays.NotificationsShade + } + } + + private val ShadeMode.qsContentKey: ContentKey + get() { + return when (this) { + ShadeMode.Single -> Scenes.QuickSettings + ShadeMode.Split -> Scenes.Shade + ShadeMode.Dual -> Overlays.QuickSettingsShade + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt index e525b86a2f9e..0fb379017be9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt @@ -21,9 +21,10 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.SceneFamilies +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -45,10 +46,14 @@ constructor( override val udfpsTransitionToFullShadeProgress = shadeRepository.udfpsTransitionToFullShadeProgress + @Deprecated("Use ShadeInteractor instead") override fun expandToNotifications() { - changeToShadeScene() + shadeInteractor.expandNotificationShade( + loggingReason = "ShadeLockscreenInteractorImpl.expandToNotifications" + ) } + @Deprecated("Use ShadeInteractor instead") override val isExpanded get() = shadeInteractor.isAnyExpanded.value @@ -60,15 +65,26 @@ constructor( lockIconViewController.dozeTimeTick() } + @Deprecated("Not supported by scenes") override fun blockExpansionForCurrentTouch() { // TODO("b/324280998") Implement replacement or delete } override fun resetViews(animate: Boolean) { + val loggingReason = "ShadeLockscreenInteractorImpl.resetViews" // The existing comment to the only call to this claims it only calls it to collapse QS - changeToShadeScene() + if (shadeInteractor.shadeMode.value == ShadeMode.Dual) { + // TODO(b/356596436): Hide without animation if !animate. + sceneInteractor.hideOverlay( + overlay = Overlays.QuickSettingsShade, + loggingReason = loggingReason, + ) + } else { + shadeInteractor.expandNotificationShade(loggingReason) + } } + @Deprecated("Not supported by scenes") override fun setPulsing(pulsing: Boolean) { // Now handled elsewhere. Do nothing. } @@ -76,22 +92,30 @@ constructor( override fun transitionToExpandedShade(delay: Long) { backgroundScope.launch { delay(delay) - withContext(mainDispatcher) { changeToShadeScene() } + withContext(mainDispatcher) { + shadeInteractor.expandNotificationShade( + "ShadeLockscreenInteractorImpl.transitionToExpandedShade" + ) + } } } + @Deprecated("Not supported by scenes") override fun resetViewGroupFade() { // Now handled elsewhere. Do nothing. } + @Deprecated("Not supported by scenes") override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) { // Now handled elsewhere. Do nothing. } + @Deprecated("Not supported by scenes") override fun setOverStretchAmount(amount: Float) { // Now handled elsewhere. Do nothing. } + @Deprecated("TODO(b/325072511) delete this") override fun setKeyguardStatusBarAlpha(alpha: Float) { // TODO(b/325072511) delete this } @@ -100,11 +124,4 @@ constructor( sceneInteractor.changeScene(Scenes.Lockscreen, "showAodUi", sceneState = KeyguardState.AOD) // TODO(b/330311871) implement transition to AOD } - - private fun changeToShadeScene() { - sceneInteractor.changeScene( - SceneFamilies.NotifShade, - "ShadeLockscreenInteractorImpl.expandToNotifications", - ) - } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt index 77ae679bf018..caa45137ed98 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt @@ -51,6 +51,10 @@ interface ShadeModeInteractor { */ val isShadeLayoutWide: StateFlow<Boolean> + /** Convenience shortcut for querying whether the current [shadeMode] is [ShadeMode.Dual]. */ + val isDualShade: Boolean + get() = shadeMode.value is ShadeMode.Dual + /** * The fraction between [0..1] (i.e., percentage) of screen width to consider the threshold * between "top-left" and "top-right" for the purposes of dual-shade invocation. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java index 0e7beb9d17da..02a29e29035d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.statusbar.notification; import android.annotation.NonNull; import android.annotation.Nullable; @@ -47,6 +47,8 @@ import com.android.systemui.statusbar.notification.data.repository.HeadsUpReposi import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; +import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply; +import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.AnimationStateHandler; import com.android.systemui.statusbar.policy.AvalancheController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpNotificationViewControllerEmptyImpl.kt index 9f76429bd6a5..021d30144b32 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpNotificationViewControllerEmptyImpl.kt @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone +package com.android.systemui.statusbar.notification import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow -import com.android.systemui.statusbar.phone.HeadsUpTouchHelper.HeadsUpNotificationViewController +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController /** Empty impl of [HeadsUpNotificationViewController] for use with Scene Container */ class HeadsUpNotificationViewControllerEmptyImpl : HeadsUpNotificationViewController { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java index 26bd7ac74d97..0927a728783f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,10 +11,10 @@ * 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 + * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.statusbar.notification; import android.content.Context; import android.os.RemoteException; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt index 2b0d2aa6ea2a..63c9e8be9ead 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.data import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone +import com.android.systemui.statusbar.notification.HeadsUpManagerPhone import dagger.Binds import dagger.Module diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 1214440a6b65..7543f3b48e48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -120,7 +120,7 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape; import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index bcdc3bc583ad..e5f63c1cb480 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -129,9 +129,9 @@ import com.android.systemui.statusbar.notification.row.NotificationSnooze; import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.phone.HeadsUpNotificationViewControllerEmptyImpl; -import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; -import com.android.systemui.statusbar.phone.HeadsUpTouchHelper.HeadsUpNotificationViewController; +import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl; +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt index 0d0f2cd50a29..7919c84152b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt @@ -1,6 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.statusbar.phone import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.HeadsUpManagerPhone import com.android.systemui.statusbar.policy.HeadsUpManager import dagger.Binds import dagger.Module diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index ca9436315dcc..c089092c9b86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -31,7 +31,6 @@ import androidx.annotation.WorkerThread; import com.android.internal.annotations.GuardedBy; import com.android.settingslib.bluetooth.BluetoothCallback; -import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfile; @@ -72,7 +71,6 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private final LocalBluetoothManager mLocalBluetoothManager; private final UserManager mUserManager; private final int mCurrentUser; - private final Context mContext; @GuardedBy("mConnectedDevices") private final List<CachedBluetoothDevice> mConnectedDevices = new ArrayList<>(); @@ -101,7 +99,6 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa @Main Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager, @Nullable BluetoothAdapter bluetoothAdapter) { - mContext = context; mDumpManager = dumpManager; mLogger = logger; mBluetoothRepository = bluetoothRepository; @@ -265,21 +262,9 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } private Collection<CachedBluetoothDevice> getDevices() { - Collection<CachedBluetoothDevice> devices = - mLocalBluetoothManager != null - ? mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy() - : Collections.emptyList(); - if (com.android.settingslib.flags.Flags.enableHideExclusivelyManagedBluetoothDevice()) { - // When the device is exclusively managed by its owner app it needs to be hidden. - devices = - devices.stream() - .filter( - device -> - !BluetoothUtils.isExclusivelyManagedBluetoothDevice( - mContext, device.getDevice())) - .toList(); - } - return devices; + return mLocalBluetoothManager != null + ? mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy() + : Collections.emptyList(); } private void updateConnected() { 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 a3b186744537..411ff8b2b542 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 @@ -24,8 +24,6 @@ import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenCon import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureMonitor -import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState -import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor @Composable fun BackGestureTutorialScreen( @@ -49,14 +47,11 @@ fun BackGestureTutorialScreen( ) ) val gestureMonitorProvider = - object : GestureMonitorProvider { - override fun createGestureMonitor( - gestureDistanceThresholdPx: Int, - gestureStateChangedCallback: (GestureState) -> Unit - ): TouchpadGestureMonitor { - return BackGestureMonitor(gestureDistanceThresholdPx, gestureStateChangedCallback) + DistanceBasedGestureMonitorProvider( + monitorFactory = { distanceThresholdPx, gestureStateCallback -> + BackGestureMonitor(distanceThresholdPx, gestureStateCallback) } - } + ) GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack) } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt index 57d7c84af4ba..0ecbf70f85c3 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt @@ -16,6 +16,7 @@ package com.android.systemui.touchpad.tutorial.ui.composable +import android.content.res.Resources import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope @@ -39,12 +40,35 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureHandler import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor interface GestureMonitorProvider { - fun createGestureMonitor( - gestureDistanceThresholdPx: Int, + + @Composable + fun rememberGestureMonitor( + resources: Resources, gestureStateChangedCallback: (GestureState) -> Unit ): TouchpadGestureMonitor } +typealias gestureStateCallback = (GestureState) -> Unit + +class DistanceBasedGestureMonitorProvider( + val monitorFactory: (Int, gestureStateCallback) -> TouchpadGestureMonitor +) : GestureMonitorProvider { + + @Composable + override fun rememberGestureMonitor( + resources: Resources, + gestureStateChangedCallback: (GestureState) -> Unit + ): TouchpadGestureMonitor { + val distanceThresholdPx = + resources.getDimensionPixelSize( + com.android.internal.R.dimen.system_gestures_distance_threshold + ) + return remember(distanceThresholdPx) { + monitorFactory(distanceThresholdPx, gestureStateChangedCallback) + } + } +} + fun GestureState.toTutorialActionState(): TutorialActionState { return when (this) { NOT_STARTED -> TutorialActionState.NOT_STARTED @@ -62,19 +86,12 @@ fun GestureTutorialScreen( ) { BackHandler(onBack = onBack) var gestureState by remember { mutableStateOf(NOT_STARTED) } - val swipeDistanceThresholdPx = - LocalContext.current.resources.getDimensionPixelSize( - com.android.internal.R.dimen.system_gestures_distance_threshold + val gestureMonitor = + gestureMonitorProvider.rememberGestureMonitor( + resources = LocalContext.current.resources, + gestureStateChangedCallback = { gestureState = it } ) - val gestureHandler = - remember(swipeDistanceThresholdPx) { - TouchpadGestureHandler( - gestureMonitorProvider.createGestureMonitor( - swipeDistanceThresholdPx, - gestureStateChangedCallback = { gestureState = it } - ) - ) - } + val gestureHandler = remember(gestureMonitor) { TouchpadGestureHandler(gestureMonitor) } TouchpadGesturesHandlingBox(gestureHandler, gestureState) { ActionTutorialContent( gestureState.toTutorialActionState(), diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt index d4eb0cd7327b..f2fec5f5d9b1 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt @@ -23,9 +23,7 @@ import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty import com.android.systemui.res.R -import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureMonitor -import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor @Composable fun HomeGestureTutorialScreen( @@ -49,14 +47,11 @@ fun HomeGestureTutorialScreen( ) ) val gestureMonitorProvider = - object : GestureMonitorProvider { - override fun createGestureMonitor( - gestureDistanceThresholdPx: Int, - gestureStateChangedCallback: (GestureState) -> Unit - ): TouchpadGestureMonitor { - return HomeGestureMonitor(gestureDistanceThresholdPx, gestureStateChangedCallback) + DistanceBasedGestureMonitorProvider( + monitorFactory = { distanceThresholdPx, gestureStateCallback -> + HomeGestureMonitor(distanceThresholdPx, gestureStateCallback) } - } + ) GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack) } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt new file mode 100644 index 000000000000..b2fb6cdfcee5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.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.systemui.touchpad.tutorial.ui.composable + +import android.content.res.Resources +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import com.airbnb.lottie.compose.rememberLottieDynamicProperties +import com.android.compose.theme.LocalAndroidColorScheme +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig +import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty +import com.android.systemui.res.R +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState +import com.android.systemui.touchpad.tutorial.ui.gesture.RecentAppsGestureMonitor +import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor + +@Composable +fun RecentAppsGestureTutorialScreen( + onDoneButtonClicked: () -> Unit, + onBack: () -> Unit, +) { + val screenConfig = + TutorialScreenConfig( + colors = rememberScreenColors(), + strings = + TutorialScreenConfig.Strings( + titleResId = R.string.touchpad_recent_apps_gesture_action_title, + bodyResId = R.string.touchpad_recent_apps_gesture_guidance, + titleSuccessResId = R.string.touchpad_recent_apps_gesture_success_title, + bodySuccessResId = R.string.touchpad_recent_apps_gesture_success_body + ), + animations = + TutorialScreenConfig.Animations( + educationResId = R.raw.trackpad_recent_apps_edu, + successResId = R.raw.trackpad_recent_apps_success + ) + ) + val gestureMonitorProvider = + object : GestureMonitorProvider { + @Composable + override fun rememberGestureMonitor( + resources: Resources, + gestureStateChangedCallback: (GestureState) -> Unit + ): TouchpadGestureMonitor { + val distanceThresholdPx = + resources.getDimensionPixelSize( + com.android.internal.R.dimen.system_gestures_distance_threshold + ) + val velocityThresholdPxPerMs = + resources.getDimension(R.dimen.touchpad_recent_apps_gesture_velocity_threshold) + return remember(distanceThresholdPx, velocityThresholdPxPerMs) { + RecentAppsGestureMonitor( + distanceThresholdPx, + gestureStateChangedCallback, + velocityThresholdPxPerMs + ) + } + } + } + GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack) +} + +@Composable +private fun rememberScreenColors(): TutorialScreenConfig.Colors { + val secondaryFixedDim = LocalAndroidColorScheme.current.secondaryFixedDim + val onSecondaryFixed = LocalAndroidColorScheme.current.onSecondaryFixed + val onSecondaryFixedVariant = LocalAndroidColorScheme.current.onSecondaryFixedVariant + val dynamicProperties = + rememberLottieDynamicProperties( + rememberColorFilterProperty(".secondaryFixedDim", secondaryFixedDim), + rememberColorFilterProperty(".onSecondaryFixed", onSecondaryFixed), + rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant) + ) + val screenColors = + remember(dynamicProperties) { + TutorialScreenConfig.Colors( + background = onSecondaryFixed, + title = secondaryFixedDim, + animationColors = dynamicProperties, + ) + } + return screenColors +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt index 65b452a81da8..5a77c04924dd 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt @@ -41,7 +41,7 @@ import com.android.systemui.res.R fun TutorialSelectionScreen( onBackTutorialClicked: () -> Unit, onHomeTutorialClicked: () -> Unit, - onActionKeyTutorialClicked: () -> Unit, + onRecentAppsTutorialClicked: () -> Unit, onDoneButtonClicked: () -> Unit, ) { Column( @@ -55,7 +55,7 @@ fun TutorialSelectionScreen( TutorialSelectionButtons( onBackTutorialClicked = onBackTutorialClicked, onHomeTutorialClicked = onHomeTutorialClicked, - onActionKeyTutorialClicked = onActionKeyTutorialClicked, + onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, modifier = Modifier.padding(60.dp) ) DoneButton( @@ -69,7 +69,7 @@ fun TutorialSelectionScreen( private fun TutorialSelectionButtons( onBackTutorialClicked: () -> Unit, onHomeTutorialClicked: () -> Unit, - onActionKeyTutorialClicked: () -> Unit, + onRecentAppsTutorialClicked: () -> Unit, modifier: Modifier = Modifier ) { Row( @@ -90,8 +90,8 @@ private fun TutorialSelectionButtons( modifier = Modifier.weight(1f) ) TutorialButton( - text = stringResource(R.string.touchpad_tutorial_action_key_button), - onClick = onActionKeyTutorialClicked, + text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button), + onClick = onRecentAppsTutorialClicked, color = MaterialTheme.colorScheme.tertiary, modifier = Modifier.weight(1f) ) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt index 821b51a18a8e..46ea352ff85a 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt @@ -32,10 +32,12 @@ import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.Tutor import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen +import com.android.systemui.touchpad.tutorial.ui.composable.RecentAppsGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.TutorialSelectionScreen import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.ACTION_KEY import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.BACK_GESTURE import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.HOME_GESTURE +import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.RECENT_APPS_GESTURE import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.TUTORIAL_SELECTION import com.android.systemui.touchpad.tutorial.ui.viewmodel.TouchpadTutorialViewModel import javax.inject.Inject @@ -84,7 +86,7 @@ fun TouchpadTutorialScreen(vm: TouchpadTutorialViewModel, closeTutorial: () -> U TutorialSelectionScreen( onBackTutorialClicked = { vm.goTo(BACK_GESTURE) }, onHomeTutorialClicked = { vm.goTo(HOME_GESTURE) }, - onActionKeyTutorialClicked = { vm.goTo(ACTION_KEY) }, + onRecentAppsTutorialClicked = { vm.goTo(RECENT_APPS_GESTURE) }, onDoneButtonClicked = closeTutorial ) BACK_GESTURE -> @@ -97,6 +99,11 @@ fun TouchpadTutorialScreen(vm: TouchpadTutorialViewModel, closeTutorial: () -> U onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) }, onBack = { vm.goTo(TUTORIAL_SELECTION) }, ) + RECENT_APPS_GESTURE -> + RecentAppsGestureTutorialScreen( + onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) }, + onBack = { vm.goTo(TUTORIAL_SELECTION) }, + ) ACTION_KEY -> // TODO(b/358105049) move action key tutorial to OOBE flow ActionKeyTutorialScreen( onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) }, diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt index 43266ad8da76..599e1b1eff75 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt @@ -64,5 +64,6 @@ enum class Screen { TUTORIAL_SELECTION, BACK_GESTURE, HOME_GESTURE, + RECENT_APPS_GESTURE, ACTION_KEY, } diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt index b02cccc2bb8d..4959224ead2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt @@ -146,9 +146,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { whenever(packageManager.resolveServiceAsUser(any(), anyInt(), anyInt())) .thenReturn(mock(ResolveInfo::class.java)) - mSetFlagsRule.disableFlags( - com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, - ) + mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) subject = createOverviewProxyService(context) } @@ -283,6 +281,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { statusBarWinController, sysUiState, mock(), + mock(), userTracker, userManager, wakefulnessLifecycle, @@ -294,7 +293,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { dumpManager, unfoldTransitionProgressForwarder, broadcastDispatcher, - keyboardTouchpadEduStatsInteractor + keyboardTouchpadEduStatsInteractor, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java index f4d7a5bb3ba9..d58b68b5db6c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java @@ -47,7 +47,6 @@ import androidx.exifinterface.media.ExifInterface; import androidx.test.filters.MediumTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.flags.FakeFeatureFlags; import com.google.common.util.concurrent.ListenableFuture; @@ -79,7 +78,6 @@ public class ImageExporterTest extends SysuiTestCase { private static final ZonedDateTime CAPTURE_TIME = ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 13, 15), ZoneId.of("America/New_York")); - private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock private ContentResolver mMockContentResolver; @@ -125,7 +123,7 @@ public class ImageExporterTest extends SysuiTestCase { @Test public void testImageExport() throws ExecutionException, InterruptedException, IOException { ContentResolver contentResolver = mContext.getContentResolver(); - ImageExporter exporter = new ImageExporter(contentResolver, mFeatureFlags); + ImageExporter exporter = new ImageExporter(contentResolver); UUID requestId = UUID.fromString("3c11da99-9284-4863-b1d5-6f3684976814"); Bitmap original = createCheckerBitmap(10, 10, 10); @@ -191,7 +189,7 @@ public class ImageExporterTest extends SysuiTestCase { // metadata are not affected by the specified file name. final String customizedFileName = "customized_file_name"; ContentResolver contentResolver = mContext.getContentResolver(); - ImageExporter exporter = new ImageExporter(contentResolver, mFeatureFlags); + ImageExporter exporter = new ImageExporter(contentResolver); UUID requestId = UUID.fromString("3c11da99-9284-4863-b1d5-6f3684976814"); Bitmap original = createCheckerBitmap(10, 10, 10); @@ -228,7 +226,7 @@ public class ImageExporterTest extends SysuiTestCase { @Test public void testSetUser() { - ImageExporter exporter = new ImageExporter(mMockContentResolver, mFeatureFlags); + ImageExporter exporter = new ImageExporter(mMockContentResolver); UserHandle imageUserHande = UserHandle.of(10); 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 e0c4ab737511..4bd0c757543b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -167,7 +167,7 @@ import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; import com.android.systemui.statusbar.phone.KeyguardBypassController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt index 0846ced1826c..fc2ad60cfa72 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt @@ -244,23 +244,23 @@ class ShadeHeaderControllerTest : SysuiTestCase() { } @Test - fun dualCarrier_disablesCarrierIconsInStatusIcons() { + fun dualCarrier_disablesCarrierIconsInStatusIcons_qs() { whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false) makeShadeVisible() shadeHeaderController.qsExpandedFraction = 1.0f - verify(statusIcons).addIgnoredSlots(carrierIconSlots) + verify(statusIcons, times(2)).addIgnoredSlots(carrierIconSlots) } @Test - fun dualCarrier_enablesCarrierIconsInStatusIcons_qsExpanded() { + fun dualCarrier_disablesCarrierIconsInStatusIcons_qqs() { whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false) makeShadeVisible() shadeHeaderController.qsExpandedFraction = 0.0f - verify(statusIcons, times(2)).removeIgnoredSlots(carrierIconSlots) + verify(statusIcons, times(2)).addIgnoredSlots(carrierIconSlots) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt index b65a90200837..a1750cdd0c84 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.shade.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 @@ -27,16 +29,18 @@ 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.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.shadeTestUtil +import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.testKosmos -import com.google.common.truth.Truth import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -53,7 +57,12 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { private val sceneInteractor = kosmos.sceneInteractor private val shadeTestUtil = kosmos.shadeTestUtil - private val underTest = kosmos.shadeInteractorSceneContainerImpl + private lateinit var underTest: ShadeInteractorSceneContainerImpl + + @Before + fun setUp() { + underTest = kosmos.shadeInteractorSceneContainerImpl + } @Test fun qsExpansionWhenInSplitShadeAndQsExpanded() = @@ -80,7 +89,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { keyguardRepository.setStatusBarState(StatusBarState.SHADE) // THEN legacy shade expansion is passed through - Truth.assertThat(actual).isEqualTo(.3f) + assertThat(actual).isEqualTo(.3f) } @Test @@ -109,7 +118,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { runCurrent() // THEN shade expansion is zero - Truth.assertThat(actual).isEqualTo(.7f) + assertThat(actual).isEqualTo(.7f) } @Test @@ -134,7 +143,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { runCurrent() // THEN QS is not fullscreen - Truth.assertThat(actual).isFalse() + assertThat(actual).isFalse() } @Test @@ -152,7 +161,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { runCurrent() // THEN QS is not fullscreen - Truth.assertThat(actual).isFalse() + assertThat(actual).isFalse() } @Test @@ -171,7 +180,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { runCurrent() // THEN QS is not fullscreen - Truth.assertThat(actual).isFalse() + assertThat(actual).isFalse() } @Test @@ -189,7 +198,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { runCurrent() // THEN QS is fullscreen - Truth.assertThat(actual).isTrue() + assertThat(actual).isTrue() } @Test @@ -206,7 +215,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { sceneInteractor.setTransitionState(transitionState) // THEN expansion is 1 - Truth.assertThat(expansionAmount).isEqualTo(1f) + assertThat(expansionAmount).isEqualTo(1f) } @Test @@ -224,7 +233,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { sceneInteractor.setTransitionState(transitionState) // THEN expansion is 0 - Truth.assertThat(expansionAmount).isEqualTo(0f) + assertThat(expansionAmount).isEqualTo(0f) } @Test @@ -251,19 +260,19 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { sceneInteractor.setTransitionState(transitionState) // THEN expansion is 0 - Truth.assertThat(expansionAmount).isEqualTo(0f) + assertThat(expansionAmount).isEqualTo(0f) // WHEN transition state is partially to the scene progress.value = .4f // THEN expansion matches the progress - Truth.assertThat(expansionAmount).isEqualTo(.4f) + assertThat(expansionAmount).isEqualTo(.4f) // WHEN transition completes progress.value = 1f // THEN expansion is 1 - Truth.assertThat(expansionAmount).isEqualTo(1f) + assertThat(expansionAmount).isEqualTo(1f) } @Test @@ -290,19 +299,19 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { sceneInteractor.setTransitionState(transitionState) // THEN expansion is 1 - Truth.assertThat(expansionAmount).isEqualTo(1f) + assertThat(expansionAmount).isEqualTo(1f) // WHEN transition state is partially to the scene progress.value = .4f // THEN expansion reflects the progress - Truth.assertThat(expansionAmount).isEqualTo(.6f) + assertThat(expansionAmount).isEqualTo(.6f) // WHEN transition completes progress.value = 1f // THEN expansion is 0 - Truth.assertThat(expansionAmount).isEqualTo(0f) + assertThat(expansionAmount).isEqualTo(0f) } fun isQsBypassingShade_goneToQs() = @@ -326,7 +335,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { runCurrent() // THEN qs is bypassing shade - Truth.assertThat(actual).isTrue() + assertThat(actual).isTrue() } fun isQsBypassingShade_shadeToQs() = @@ -350,7 +359,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { runCurrent() // THEN qs is not bypassing shade - Truth.assertThat(actual).isFalse() + assertThat(actual).isFalse() } @Test @@ -376,19 +385,19 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { sceneInteractor.setTransitionState(transitionState) // THEN expansion is 0 - Truth.assertThat(expansionAmount).isEqualTo(0f) + assertThat(expansionAmount).isEqualTo(0f) // WHEN transition state is partially complete progress.value = .4f // THEN expansion is still 0 - Truth.assertThat(expansionAmount).isEqualTo(0f) + assertThat(expansionAmount).isEqualTo(0f) // WHEN transition completes progress.value = 1f // THEN expansion is still 0 - Truth.assertThat(expansionAmount).isEqualTo(0f) + assertThat(expansionAmount).isEqualTo(0f) } @Test @@ -405,7 +414,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { sceneInteractor.setTransitionState(transitionState) // THEN interacting is false - Truth.assertThat(interacting).isFalse() + assertThat(interacting).isFalse() } @Test @@ -432,19 +441,19 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { sceneInteractor.setTransitionState(transitionState) // THEN interacting is false - Truth.assertThat(interacting).isFalse() + assertThat(interacting).isFalse() // WHEN transition state is partially to the scene progress.value = .4f // THEN interacting is false - Truth.assertThat(interacting).isFalse() + assertThat(interacting).isFalse() // WHEN transition completes progress.value = 1f // THEN interacting is false - Truth.assertThat(interacting).isFalse() + assertThat(interacting).isFalse() } @Test @@ -471,19 +480,19 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { sceneInteractor.setTransitionState(transitionState) // THEN interacting is true - Truth.assertThat(interacting).isTrue() + assertThat(interacting).isTrue() // WHEN transition state is partially to the scene progress.value = .4f // THEN interacting is true - Truth.assertThat(interacting).isTrue() + assertThat(interacting).isTrue() // WHEN transition completes progress.value = 1f // THEN interacting is true - Truth.assertThat(interacting).isTrue() + assertThat(interacting).isTrue() } @Test @@ -510,19 +519,19 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { sceneInteractor.setTransitionState(transitionState) // THEN interacting is false - Truth.assertThat(interacting).isFalse() + assertThat(interacting).isFalse() // WHEN transition state is partially to the scene progress.value = .4f // THEN interacting is false - Truth.assertThat(interacting).isFalse() + assertThat(interacting).isFalse() // WHEN transition completes progress.value = 1f // THEN interacting is false - Truth.assertThat(interacting).isFalse() + assertThat(interacting).isFalse() } @Test @@ -549,19 +558,19 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { sceneInteractor.setTransitionState(transitionState) // THEN interacting is true - Truth.assertThat(interacting).isTrue() + assertThat(interacting).isTrue() // WHEN transition state is partially to the scene progress.value = .4f // THEN interacting is true - Truth.assertThat(interacting).isTrue() + assertThat(interacting).isTrue() // WHEN transition completes progress.value = 1f // THEN interacting is true - Truth.assertThat(interacting).isTrue() + assertThat(interacting).isTrue() } @Test @@ -572,7 +581,6 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { val interacting by collectLastValue(interactingFlow) // WHEN transition state is starting to between different scenes - val progress = MutableStateFlow(0f) val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( @@ -589,4 +597,94 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { // THEN interacting is false assertThat(interacting).isFalse() } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun expandNotificationShade_dualShadeEnabled_opensOverlay() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(currentOverlays).isEmpty() + + underTest.expandNotificationShade("reason") + + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade) + } + + @Test + @DisableFlags(DualShade.FLAG_NAME) + fun expandNotificationShade_dualShadeDisabled_switchesToShadeScene() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(currentOverlays).isEmpty() + + underTest.expandNotificationShade("reason") + + assertThat(currentScene).isEqualTo(Scenes.Shade) + assertThat(currentOverlays).isEmpty() + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun expandNotificationShade_dualShadeEnabledAndQuickSettingsOpen_replacesOverlay() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) + underTest.expandQuickSettingsShade("reason") + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(currentOverlays).containsExactly(Overlays.QuickSettingsShade) + + underTest.expandNotificationShade("reason") + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade) + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun expandQuickSettingsShade_dualShadeEnabled_opensOverlay() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(currentOverlays).isEmpty() + + underTest.expandQuickSettingsShade("reason") + + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(currentOverlays).containsExactly(Overlays.QuickSettingsShade) + } + + @Test + @DisableFlags(DualShade.FLAG_NAME) + fun expandQuickSettingsShade_dualShadeDisabled_switchesToQuickSettingsScene() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(currentOverlays).isEmpty() + + underTest.expandQuickSettingsShade("reason") + + assertThat(currentScene).isEqualTo(Scenes.QuickSettings) + assertThat(currentOverlays).isEmpty() + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun expandQuickSettingsShade_dualShadeEnabledAndNotificationsOpen_replacesOverlay() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) + underTest.expandNotificationShade("reason") + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade) + + underTest.expandQuickSettingsShade("reason") + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(currentOverlays).containsExactly(Overlays.QuickSettingsShade) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt index b4f4138fd409..76bb8de71bdd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -43,7 +43,7 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone +import com.android.systemui.statusbar.notification.HeadsUpManagerPhone import com.android.systemui.statusbar.phone.NotificationGroupTestHelper import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener import com.android.systemui.util.concurrency.FakeExecutor 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 3df4a677b9e5..30556bef6af4 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 @@ -103,7 +103,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; +import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index 93071bd9305a..2588f1f4fe3f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -14,10 +14,6 @@ package com.android.systemui.statusbar.policy; -import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; - -import static com.android.settingslib.flags.Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE; - import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; @@ -25,7 +21,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; @@ -35,11 +30,7 @@ import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; -import android.platform.test.flag.junit.FlagsParameterization; +import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -65,31 +56,16 @@ import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - -@RunWith(ParameterizedAndroidJunit4.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest public class BluetoothControllerImplTest extends SysuiTestCase { - @Parameters(name = "{0}") - public static List<FlagsParameterization> getParams() { - return allCombinationsOf(FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE); - } - - private static final String TEST_EXCLUSIVE_MANAGER = "com.test.manager"; - - @Mock - private PackageManager mPackageManager; - private UserTracker mUserTracker; private LocalBluetoothManager mMockBluetoothManager; private CachedBluetoothDeviceManager mMockDeviceManager; @@ -102,21 +78,14 @@ public class BluetoothControllerImplTest extends SysuiTestCase { private FakeExecutor mBackgroundExecutor; - public BluetoothControllerImplTest(FlagsParameterization flags) { - super(); - mSetFlagsRule.setFlagsParameterization(flags); - } - @Before public void setup() throws Exception { - MockitoAnnotations.initMocks(this); mTestableLooper = TestableLooper.get(this); mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class); mDevices = new ArrayList<>(); mUserTracker = mock(UserTracker.class); mMockDeviceManager = mock(CachedBluetoothDeviceManager.class); mMockAdapter = mock(BluetoothAdapter.class); - mContext.setMockPackageManager(mPackageManager); when(mMockDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices); when(mMockBluetoothManager.getCachedDeviceManager()).thenReturn(mMockDeviceManager); mMockLocalAdapter = mock(LocalBluetoothAdapter.class); @@ -146,7 +115,6 @@ public class BluetoothControllerImplTest extends SysuiTestCase { CachedBluetoothDevice device = mock(CachedBluetoothDevice.class); when(device.isConnected()).thenReturn(true); when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED); - when(device.getDevice()).thenReturn(mock(BluetoothDevice.class)); mDevices.add(device); when(mMockLocalAdapter.getConnectionState()) @@ -172,12 +140,10 @@ public class BluetoothControllerImplTest extends SysuiTestCase { public void getConnectedDevices_onlyReturnsConnected() { CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class); when(device1Disconnected.isConnected()).thenReturn(false); - when(device1Disconnected.getDevice()).thenReturn(mock(BluetoothDevice.class)); mDevices.add(device1Disconnected); CachedBluetoothDevice device2Connected = mock(CachedBluetoothDevice.class); when(device2Connected.isConnected()).thenReturn(true); - when(device2Connected.getDevice()).thenReturn(mock(BluetoothDevice.class)); mDevices.add(device2Connected); mBluetoothControllerImpl.onDeviceAdded(device1Disconnected); @@ -189,46 +155,6 @@ public class BluetoothControllerImplTest extends SysuiTestCase { } @Test - @EnableFlags(FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) - public void getConnectedDevice_exclusivelyManagedDevice_doNotReturn() - throws PackageManager.NameNotFoundException { - CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); - when(cachedDevice.isConnected()).thenReturn(true); - BluetoothDevice device = mock(BluetoothDevice.class); - when(device.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - TEST_EXCLUSIVE_MANAGER.getBytes()); - when(cachedDevice.getDevice()).thenReturn(device); - doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo( - TEST_EXCLUSIVE_MANAGER, 0); - - mDevices.add(cachedDevice); - mBluetoothControllerImpl.onDeviceAdded(cachedDevice); - - assertThat(mBluetoothControllerImpl.getConnectedDevices()).isEmpty(); - } - - @Test - @DisableFlags(FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) - public void getConnectedDevice_exclusivelyManagedDevice_returnsConnected() - throws PackageManager.NameNotFoundException { - CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); - when(cachedDevice.isConnected()).thenReturn(true); - BluetoothDevice device = mock(BluetoothDevice.class); - when(device.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - TEST_EXCLUSIVE_MANAGER.getBytes()); - when(cachedDevice.getDevice()).thenReturn(device); - doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo( - TEST_EXCLUSIVE_MANAGER, 0); - - mDevices.add(cachedDevice); - mBluetoothControllerImpl.onDeviceAdded(cachedDevice); - - assertThat(mBluetoothControllerImpl.getConnectedDevices()).hasSize(1); - assertThat(mBluetoothControllerImpl.getConnectedDevices().get(0)) - .isEqualTo(cachedDevice); - } - - @Test public void testOnBluetoothStateChange_updatesBluetoothState() { mBluetoothControllerImpl.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF); @@ -259,7 +185,6 @@ public class BluetoothControllerImplTest extends SysuiTestCase { assertFalse(mBluetoothControllerImpl.isBluetoothConnected()); CachedBluetoothDevice device = mock(CachedBluetoothDevice.class); - when(device.getDevice()).thenReturn(mock(BluetoothDevice.class)); mDevices.add(device); when(device.isConnected()).thenReturn(true); when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED); @@ -478,7 +403,6 @@ public class BluetoothControllerImplTest extends SysuiTestCase { private CachedBluetoothDevice createBluetoothDevice( int profile, boolean isConnected, boolean isActive) { CachedBluetoothDevice device = mock(CachedBluetoothDevice.class); - when(device.getDevice()).thenReturn(mock(BluetoothDevice.class)); mDevices.add(device); when(device.isActiveDevice(profile)).thenReturn(isActive); diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt index cafebb9fd79e..d059c14785a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt @@ -38,8 +38,7 @@ class RecentAppsGestureMonitorTest : SysuiTestCase() { companion object { const val THRESHOLD_VELOCITY_PX_PER_MS = 0.1f - // multiply by 1000 to get px per ms instead of px per s which is unit used by velocity - // tracker + // multiply by 1000 to get px/ms instead of px/s which is unit used by velocity tracker const val SLOW = THRESHOLD_VELOCITY_PX_PER_MS * 1000 - 1 const val FAST = THRESHOLD_VELOCITY_PX_PER_MS * 1000 + 1 } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt index 04d930c72792..92075ea75c4a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt @@ -44,7 +44,7 @@ val Kosmos.shadeInteractorSceneContainerImpl by ShadeInteractorSceneContainerImpl( scope = applicationCoroutineScope, sceneInteractor = sceneInteractor, - shadeRepository = shadeRepository, + shadeModeInteractor = shadeModeInteractor, ) } val Kosmos.shadeInteractorLegacyImpl by diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 3c65f378f33d..d0ea47417622 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -5,6 +5,10 @@ package { // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 default_applicable_licenses: ["frameworks_base_license"], + + // OWNER: g/ravenwood + // Bug component: 25698 + default_team: "trendy_team_framework_backstage_power", } filegroup { diff --git a/ravenwood/OWNERS b/ravenwood/OWNERS index f7b13d19dbb3..8722ad92ff49 100644 --- a/ravenwood/OWNERS +++ b/ravenwood/OWNERS @@ -1,5 +1,7 @@ +# Bug component: 25698 set noparent +omakoto@google.com topjohnwu@google.com hackbod@google.com #{LAST_RESORT_SUGGESTION} diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index f83269f86cd2..269419f70b2f 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -31,6 +31,7 @@ import android.os.Binder; import android.os.UserHandle; import android.text.TextUtils; import android.util.Slog; +import android.app.appsearch.AppSearchResult; import com.android.internal.annotations.VisibleForTesting; import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback; @@ -81,6 +82,17 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { final SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback = new SafeOneTimeExecuteAppFunctionCallback(executeAppFunctionCallback); + try { + executeAppFunctionInternal(requestInternal, safeExecuteAppFunctionCallback); + } catch (Exception e) { + safeExecuteAppFunctionCallback.onResult(mapExceptionToExecuteAppFunctionResponse(e)); + } + } + + private void executeAppFunctionInternal( + ExecuteAppFunctionAidlRequest requestInternal, + SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) { + String validatedCallingPackage; UserHandle targetUser; try { @@ -119,7 +131,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { return; } - mCallerValidator + var unused = mCallerValidator .verifyCallerCanExecuteAppFunction( validatedCallingPackage, targetPackageName, @@ -159,6 +171,12 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } finally { Binder.restoreCallingIdentity(token); } + }) + .exceptionally( + ex -> { + safeExecuteAppFunctionCallback.onResult( + mapExceptionToExecuteAppFunctionResponse(ex)); + return null; }); } @@ -235,4 +253,37 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { /* extras= */ null)); } } + + private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) { + if (e instanceof AppSearchException) { + AppSearchException appSearchException = (AppSearchException) e; + return ExecuteAppFunctionResponse.newFailure( + mapAppSearchResultFailureCodeToExecuteAppFunctionResponse( + appSearchException.getResultCode()), + appSearchException.getMessage(), + /* extras= */ null); + } + + return ExecuteAppFunctionResponse.newFailure( + ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR, + e.getMessage(), + /* extras= */ null); + } + + private int mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(int resultCode) { + if (resultCode == AppSearchResult.RESULT_OK) { + throw new IllegalArgumentException( + "This method can only be used to convert failure result codes."); + } + + switch (resultCode) { + case AppSearchResult.RESULT_NOT_FOUND: + return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT; + case AppSearchResult.RESULT_INVALID_ARGUMENT: + case AppSearchResult.RESULT_INTERNAL_ERROR: + case AppSearchResult.RESULT_SECURITY_ERROR: + // fall-through + } + return ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR; + } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppSearchException.java b/services/appfunctions/java/com/android/server/appfunctions/AppSearchException.java new file mode 100644 index 000000000000..c23470a68c70 --- /dev/null +++ b/services/appfunctions/java/com/android/server/appfunctions/AppSearchException.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appfunctions; + +import android.app.appsearch.AppSearchResult; + +/** Exception to wrap failure result codes returned by AppSearch. */ +public class AppSearchException extends RuntimeException { + private final int resultCode; + + public AppSearchException(int resultCode, String message) { + super(message); + this.resultCode = resultCode; + } + + /** + * Returns the result code used to create this exception, typically one of the {@link + * AppSearchResult} result codes. + */ + public int getResultCode() { + return resultCode; + } +} diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java index 99984384f455..c4ccdd89ffae 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java @@ -30,6 +30,7 @@ import android.app.admin.DevicePolicyManager; import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchManager.SearchContext; +import android.app.appsearch.AppSearchResult; import android.app.appsearch.GenericDocument; import android.app.appsearch.GetByDocumentIdRequest; import android.content.Context; @@ -38,8 +39,9 @@ import android.os.Binder; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; -import java.util.Objects; + import com.android.internal.infra.AndroidFuture; +import java.util.Objects; /* Validates that caller has the correct privilege to call an AppFunctionManager Api. */ class CallerValidatorImpl implements CallerValidator { @@ -144,7 +146,14 @@ class CallerValidatorImpl implements CallerValidator { if (result.isSuccess()) { return result.getSuccesses().get(documentId); } - throw new IllegalArgumentException("No document in the result for id: " + documentId); + + AppSearchResult<GenericDocument> failedResult = result.getFailures().get(documentId); + throw new AppSearchException( + failedResult.getResultCode(), + "Unable to retrieve document with id: " + + documentId + + " due to " + + failedResult.getErrorMessage()); } private static boolean getRestrictCallersWithExecuteAppFunctionsProperty( diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index f5a297bfd4f5..6fd281e7003e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -685,6 +685,11 @@ final class ActivityManagerConstants extends ContentObserver { // default. Controlled by Settings.Global.FORCE_ENABLE_PSS_PROFILING volatile boolean mForceEnablePssProfiling = false; + // Indicates whether to use ApplicationInfo to determine launched state instead of PM user state + // This is a temporary workaround until the trunk-stable flag is pushed to nextfood. + // TODO: b/365979852 - remove this workaround when redundant + volatile boolean mFlagUseAppInfoNotLaunched = false; + /** * Indicates whether the foreground service background start restriction is enabled for * caller app that is targeting S+. @@ -1017,6 +1022,9 @@ final class ActivityManagerConstants extends ContentObserver { private static final Uri FORCE_ENABLE_PSS_PROFILING_URI = Settings.Global.getUriFor(Settings.Global.FORCE_ENABLE_PSS_PROFILING); + private static final Uri ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI = + Settings.Global.getUriFor(Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED); + /** * The threshold to decide if a given association should be dumped into metrics. */ @@ -1479,6 +1487,7 @@ final class ActivityManagerConstants extends ContentObserver { false, this); } mResolver.registerContentObserver(FORCE_ENABLE_PSS_PROFILING_URI, false, this); + mResolver.registerContentObserver(ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI, false, this); updateConstants(); if (mSystemServerAutomaticHeapDumpEnabled) { updateEnableAutomaticSystemServerHeapDumps(); @@ -1495,6 +1504,7 @@ final class ActivityManagerConstants extends ContentObserver { updateActivityStartsLoggingEnabled(); updateForegroundServiceStartsLoggingEnabled(); updateForceEnablePssProfiling(); + updateEnableUseAppInfoNotLaunched(); // Read DropboxRateLimiter params from flags. mService.initDropboxRateLimiter(); } @@ -1540,6 +1550,8 @@ final class ActivityManagerConstants extends ContentObserver { updateEnableAutomaticSystemServerHeapDumps(); } else if (FORCE_ENABLE_PSS_PROFILING_URI.equals(uri)) { updateForceEnablePssProfiling(); + } else if (ENABLE_USE_APP_INFO_NOT_LAUNCHED_URI.equals(uri)) { + updateEnableUseAppInfoNotLaunched(); } } @@ -1659,6 +1671,11 @@ final class ActivityManagerConstants extends ContentObserver { Settings.Global.FORCE_ENABLE_PSS_PROFILING, 0) == 1; } + private void updateEnableUseAppInfoNotLaunched() { + mFlagUseAppInfoNotLaunched = Settings.Global.getInt(mResolver, + Settings.Global.ENABLE_USE_APP_INFO_NOT_LAUNCHED, 0) == 1; + } + private void updateBackgroundActivityStarts() { mFlagBackgroundActivityStartsEnabled = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -2538,6 +2555,8 @@ final class ActivityManagerConstants extends ContentObserver { pw.print(" OOMADJ_UPDATE_QUICK="); pw.println(OOMADJ_UPDATE_QUICK); pw.print(" ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION="); pw.println(mEnableWaitForFinishAttachApplication); + pw.print(" FLAG_USE_APP_INFO_NOT_LAUNCHED="); + pw.println(mFlagUseAppInfoNotLaunched); pw.print(" "); pw.print(KEY_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION); pw.print("="); pw.println(FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 3f4902db70f5..2416ab68c1af 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1018,7 +1018,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub if (Flags.addBatteryUsageStatsSliceAtom()) { statsManager.setPullAtomCallback( FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID, - null, // use default PullAtomMetadata values + new StatsManager.PullAtomMetadata.Builder() + .setTimeoutMillis(3_000L) + .build(), DIRECT_EXECUTOR, pullAtomCallback); } @@ -1098,14 +1100,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub DEVICE_CONFIG_NAMESPACE, MIN_CONSUMED_POWER_THRESHOLD_KEY, 0); - final long sessionStart = 0; - final long sessionEnd = System.currentTimeMillis(); final BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() .setMaxStatsAgeMs(0) .includeProcessStateData() .includeVirtualUids() - .aggregateSnapshots(sessionStart, sessionEnd) .setMinConsumedPowerThreshold(minConsumedPowerThreshold) .build(); bus = getBatteryUsageStats(List.of(query)).get(0); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 4898f1095c58..cb918a045ec6 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -3398,7 +3398,8 @@ public final class ProcessList { // Check if we should mark the processrecord for first launch after force-stopping if (wasStopped) { boolean wasEverLaunched = false; - if (android.app.Flags.useAppInfoNotLaunched()) { + if (android.app.Flags.useAppInfoNotLaunched() + || mService.mConstants.mFlagUseAppInfoNotLaunched) { wasEverLaunched = !info.isNotLaunched(); } else { try { @@ -3419,7 +3420,8 @@ public final class ProcessList { : STOPPED_STATE_FIRST_LAUNCH; r.getWindowProcessController().setStoppedState(stoppedState); } else { - if (android.app.Flags.useAppInfoNotLaunched()) { + if (android.app.Flags.useAppInfoNotLaunched() + || mService.mConstants.mFlagUseAppInfoNotLaunched) { // If it was launched before, then it must be a force-stop r.setWasForceStopped(wasEverLaunched); } else { @@ -3769,7 +3771,7 @@ public final class ProcessList { boolean hasActivity = false; int connUid = 0; int connGroup = 0; - while (i >= bottomI) { + while (subProc.info.uid != uid) { mLruProcesses.remove(i); mLruProcesses.add(endIndex, subProc); if (DEBUG_LRU) Slog.d(TAG_LRU, diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig index b2e95aa1cf28..d3da8dd2cfda 100644 --- a/services/core/java/com/android/server/biometrics/biometrics.aconfig +++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig @@ -24,3 +24,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "set_ignore_speed_up" + namespace: "biometrics_framework" + description: "This flag controls whether setIgnoreDisplayTouches is called directly on session from FingerprintProvider" + bug: "359289274" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 8195efe6a56a..456591c3076f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -791,7 +791,17 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @Override public void setIgnoreDisplayTouches(long requestId, int sensorId, boolean ignoreTouches) { - mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches( + if (Flags.setIgnoreSpeedUp()) { + try { + mFingerprintSensors.get( + sensorId).getLazySession().get().getSession().setIgnoreDisplayTouches( + ignoreTouches); + Slog.d(getTag(), "setIgnoreDisplayTouches set to " + ignoreTouches); + } catch (Exception e) { + Slog.w(getTag(), "setIgnore failed", e); + } + } else { + mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches( requestId, (client) -> { if (!(client instanceof Udfps)) { Slog.e(getTag(), @@ -800,6 +810,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } ((Udfps) client).setIgnoreDisplayTouches(ignoreTouches); }); + } } @Override diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 7d44ba199119..3780fbd61e79 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -16,7 +16,6 @@ package com.android.server.locksettings; -import static android.security.Flags.reportPrimaryAuthAttempts; import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; import static android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION; import static android.Manifest.permission.MANAGE_BIOMETRIC; @@ -32,6 +31,7 @@ import static android.content.Intent.ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTO import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_SYSTEM; +import static android.security.Flags.reportPrimaryAuthAttempts; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN; @@ -1836,6 +1836,13 @@ public class LockSettingsService extends ILockSettings.Stub { } /** + * Set a new LSKF for the given user/profile. Only succeeds if the synthetic password for the + * user is protected by the given {@param savedCredential}. + * <p> + * When {@link android.security.Flags#clearStrongAuthOnAddPrimaryCredential()} is enabled and + * setting a new credential where there was none, updates the strong auth state for + * {@param userId} to <tt>STRONG_AUTH_NOT_REQUIRED</tt>. + * * @param savedCredential if the user is a profile with * {@link UserManager#isCredentialSharableWithParent()} with unified challenge and * savedCredential is empty, LSS will try to re-derive the profile password internally. @@ -1884,6 +1891,12 @@ public class LockSettingsService extends ILockSettings.Stub { onSyntheticPasswordUnlocked(userId, sp); setLockCredentialWithSpLocked(credential, sp, userId); + if (android.security.Flags.clearStrongAuthOnAddPrimaryCredential() + && savedCredential.isNone() && !credential.isNone()) { + // Clear the strong auth value, since the LSKF has just been entered and set, + // but only when the previous credential was None. + mStrongAuth.reportUnlock(userId); + } sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent); return true; } diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index cc58f38db65a..3a429b041b3c 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -1701,7 +1701,7 @@ class SyntheticPasswordManager { .setGatekeeperHAT(response.getPayload()).build(); if (response.getShouldReEnroll()) { try { - response = gatekeeper.enroll(userId, spHandle, spHandle, + response = gatekeeper.enroll(userId, spHandle, gatekeeperPassword, gatekeeperPassword); } catch (RemoteException e) { Slog.e(TAG, "Failed to invoke gatekeeper.enroll", e); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e2ec0063c711..ba7d4d218ca5 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -51,6 +51,7 @@ import static android.app.NotificationChannel.NEWS_ID; import static android.app.NotificationChannel.PROMOTIONS_ID; import static android.app.NotificationChannel.RECS_ID; import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; +import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS; import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED; import static android.app.NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED; @@ -109,6 +110,7 @@ import static android.service.notification.Adjustment.TYPE_NEWS; import static android.service.notification.Adjustment.TYPE_PROMOTION; import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA; import static android.service.notification.Flags.callstyleCallbackApi; +import static android.service.notification.Flags.notificationClassification; import static android.service.notification.Flags.notificationForceGrouping; import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle; import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners; @@ -4405,6 +4407,15 @@ public class NotificationManagerService extends SystemService { if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) { throw new IllegalArgumentException("Cannot delete default channel"); } + if (notificationClassification()) { + // Check for all reserved channels, but do not throw because it's a common + // preexisting pattern for apps to (try to) delete all channels that don't match + // their current desired channel structure + if (SYSTEM_RESERVED_IDS.contains(channelId)) { + Log.v(TAG, "Package " + pkg + " cannot delete a reserved channel"); + return; + } + } enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId); enforceDeletingChannelHasNoUserInitiatedJob(pkg, callingUser, channelId); cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 821722b15645..a4fdb758a740 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -22,6 +22,7 @@ import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID; import static android.app.NotificationChannel.PROMOTIONS_ID; import static android.app.NotificationChannel.RECS_ID; import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; +import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS; import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; @@ -440,6 +441,12 @@ public class PreferencesHelper implements RankingConfig { channel.setImportanceLockedByCriticalDeviceFunction( r.defaultAppLockedImportance || r.fixedImportance); + if (notificationClassification()) { + if (SYSTEM_RESERVED_IDS.contains(id) && channel.isDeleted() ) { + channel.setDeleted(false); + } + } + if (isShortcutOk(channel) && isDeletionOk(channel)) { r.channels.put(id, channel); } @@ -1023,6 +1030,11 @@ public class PreferencesHelper implements RankingConfig { if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) { throw new IllegalArgumentException("Reserved id"); } + // Only the user can update bundle channel settings + if (notificationClassification() && !fromSystemOrSystemUi + && SYSTEM_RESERVED_IDS.contains(channel.getId())) { + return false; + } NotificationChannel existing = r.channels.get(channel.getId()); if (existing != null && fromTargetApp) { // Actually modifying an existing channel - keep most of the existing settings diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 023f7655c7a8..ee15bec0d62b 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -92,6 +92,7 @@ import android.graphics.Rect; import android.multiuser.Flags; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IInterface; @@ -214,7 +215,7 @@ public class LauncherAppsService extends SystemService { @VisibleForTesting static class LauncherAppsImpl extends ILauncherApps.Stub { - private static final boolean DEBUG = false; + private static final boolean DEBUG = Build.IS_DEBUGGABLE; private static final String TAG = "LauncherAppsService"; private static final String NAMESPACE_MULTIUSER = "multiuser"; private static final String FLAG_NON_SYSTEM_ACCESS_TO_HIDDEN_PROFILES = @@ -495,8 +496,28 @@ public class LauncherAppsService extends SystemService { private boolean canAccessProfile(int callingUid, int callingUserId, int callingPid, int targetUserId, String message) { - if (targetUserId == callingUserId) return true; + if (DEBUG) { + final AndroidPackage callingPackage = + mPackageManagerInternal.getPackage(callingUid); + final String callingPackageName = callingPackage == null + ? null : callingPackage.getPackageName(); + Slog.v(TAG, "canAccessProfile called by " + callingPackageName + + " for user " + callingUserId + + " requesting to access user " + + targetUserId + " when invoking " + message); + } + if (targetUserId == callingUserId) { + if (DEBUG) { + Slog.v(TAG, message + " passed canAccessProfile for targetuser" + + targetUserId + " because it is the same as the calling user"); + } + return true; + } if (injectHasInteractAcrossUsersFullPermission(callingPid, callingUid)) { + if (DEBUG) { + Slog.v(TAG, message + " passed because calling process" + + "has permission to interact across users"); + } return true; } @@ -514,11 +535,25 @@ public class LauncherAppsService extends SystemService { if (isHiddenProfile(UserHandle.of(targetUserId)) && !canAccessHiddenProfile(callingUid, callingPid)) { + Slog.w(TAG, message + " for hidden profile user " + targetUserId + + " from " + callingUserId + " not allowed"); + return false; } - return mUserManagerInternal.isProfileAccessible(callingUserId, targetUserId, - message, true); + final boolean ret = mUserManagerInternal.isProfileAccessible( + callingUserId, targetUserId, message, true); + if (DEBUG) { + final AndroidPackage callingPackage = + mPackageManagerInternal.getPackage(callingUid); + final String callingPackageName = callingPackage == null + ? null : callingPackage.getPackageName(); + Slog.v(TAG, "canAccessProfile returned " + ret + " for " + callingPackageName + + " for user " + callingUserId + + " requesting to access user " + + targetUserId + " when invoking " + message); + } + return ret; } private boolean isHiddenProfile(UserHandle targetUser) { @@ -1341,6 +1376,10 @@ public class LauncherAppsService extends SystemService { @Override public void pinShortcuts(String callingPackage, String packageName, List<String> ids, UserHandle targetUser) { + if (DEBUG) { + Slog.v(TAG, "pinShortcuts: " + callingPackage + " is pinning shortcuts from " + + packageName + " for user " + targetUser); + } if (!mShortcutServiceInternal .areShortcutsSupportedOnHomeScreen(targetUser.getIdentifier())) { // Requires strict ACCESS_SHORTCUTS permission for user-profiles with items @@ -1351,6 +1390,11 @@ public class LauncherAppsService extends SystemService { } ensureShortcutPermission(callingPackage); if (!canAccessProfile(targetUser.getIdentifier(), "Cannot pin shortcuts")) { + if (DEBUG) { + Slog.v(TAG, "pinShortcuts: " + callingPackage + + " is pinning shortcuts from " + packageName + + " for user " + targetUser + " but cannot access profile"); + } return; } diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java index 045d4db0a1f1..d65e30be9edb 100644 --- a/services/core/java/com/android/server/pm/ShortcutLauncher.java +++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java @@ -42,6 +42,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; /** * Launcher information used by {@link ShortcutService}. @@ -128,9 +129,15 @@ class ShortcutLauncher extends ShortcutPackageItem { */ public void pinShortcuts(@UserIdInt int packageUserId, @NonNull String packageName, @NonNull List<String> ids, boolean forPinRequest) { + if (ShortcutService.DEBUG) { + Slog.v(TAG, "ShortcutLauncher#pinShortcuts: pin shortcuts from " + packageName + + " with userId=" + packageUserId + " shortcutIds=" + + ids.stream().collect(Collectors.joining(", ", "[", "]"))); + } final ShortcutPackage packageShortcuts = mShortcutUser.getPackageShortcutsIfExists(packageName); if (packageShortcuts == null) { + Slog.w(TAG, "ShortcutLauncher#pinShortcuts packageShortcuts is null"); return; // No need to instantiate. } @@ -155,6 +162,10 @@ class ShortcutLauncher extends ShortcutPackageItem { final String id = ids.get(i); final ShortcutInfo si = packageShortcuts.findShortcutById(id); if (si == null) { + if (ShortcutService.DEBUG) { + Slog.w(TAG, "ShortcutLauncher#pinShortcuts: cannot pin " + + id + " because it does not exist"); + } continue; } if (si.isDynamic() || si.isLongLived() @@ -174,6 +185,13 @@ class ShortcutLauncher extends ShortcutPackageItem { } } } + if (ShortcutService.DEBUG) { + Slog.v(TAG, "ShortcutLauncher#pinShortcuts: " + + " newSet: " + newSet.stream().collect( + Collectors.joining(", ", "[", "]")) + + " floatingSet: " + floatingSet.stream().collect( + Collectors.joining(", ", "[", "]"))); + } mPinnedShortcuts.put(up, newSet); } } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 60056eb471d1..c9ad4988f8ca 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -729,6 +729,11 @@ class ShortcutPackage extends ShortcutPackageItem { } pinnedShortcuts.addAll(pinned); }); + if (ShortcutService.DEBUG) { + Slog.v(TAG, "ShortcutPackage#refreshPinnedFlags: " + + " pinnedShortcuts: " + pinnedShortcuts.stream().collect( + Collectors.joining(", ", "[", "]"))); + } // Secondly, update the pinned state if necessary. final List<ShortcutInfo> pinned = findAll(pinnedShortcuts); if (pinned != null) { diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index a3ff1952205f..ea495c9bee9c 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -169,7 +169,7 @@ import java.util.stream.Collectors; public class ShortcutService extends IShortcutService.Stub { static final String TAG = "ShortcutService"; - static final boolean DEBUG = false; // STOPSHIP if true + static final boolean DEBUG = Build.IS_DEBUGGABLE; // STOPSHIP if true static final boolean DEBUG_LOAD = false; // STOPSHIP if true static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true static final boolean DEBUG_REBOOT = Build.IS_DEBUGGABLE; @@ -3206,6 +3206,11 @@ public class ShortcutService extends IShortcutService.Stub { public void pinShortcuts(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull List<String> shortcutIds, int userId) { + if (DEBUG) { + Slog.v(TAG, "pinShortcuts: " + callingPackage + ", with userId=" + launcherUserId + + ", is trying to pin shortcuts from " + packageName + + " with userId=" + userId); + } // Calling permission must be checked by LauncherAppsImpl. Preconditions.checkStringNotEmpty(packageName, "packageName"); Objects.requireNonNull(shortcutIds, "shortcutIds"); @@ -3230,6 +3235,11 @@ public class ShortcutService extends IShortcutService.Stub { && !si.isDeclaredInManifest(), ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO, callingPackage, launcherUserId, false); + } else { + if (DEBUG) { + Slog.w(TAG, "specified package " + packageName + ", with userId=" + userId + + ", doesn't exist."); + } } // Get list of shortcuts that will get unpinned. ArraySet<String> oldPinnedIds = launcher.getPinnedShortcutIds(packageName, userId); @@ -5448,6 +5458,17 @@ public class ShortcutService extends IShortcutService.Stub { */ private List<ShortcutInfo> prepareChangedShortcuts(ArraySet<String> changedIds, ArraySet<String> newIds, List<ShortcutInfo> deletedList, final ShortcutPackage ps) { + if (DEBUG) { + Slog.v(TAG, "prepareChangedShortcuts: " + + " changedIds=" + (changedIds == null + ? "n/a" : changedIds.stream().collect(Collectors.joining(", ", "[", "]"))) + + " newIds=" + (newIds == null + ? "n/a" : newIds.stream().collect(Collectors.joining(", ", "[", "]"))) + + " deletedList=" + (deletedList == null + ? "n/a" : deletedList.stream().map(ShortcutInfo::getId).collect( + Collectors.joining(", ", "[", "]"))) + + " ps=" + (ps == null ? "n/a" : ps.getPackageName())); + } if (ps == null) { // This can happen when package restore is not finished yet. return null; diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java index 6847a5c699ac..dc6b1644db4d 100644 --- a/services/core/java/com/android/server/power/ThermalManagerService.java +++ b/services/core/java/com/android/server/power/ThermalManagerService.java @@ -1628,9 +1628,9 @@ public class ThermalManagerService extends SystemService { long mInactivityThresholdMillis = INACTIVITY_THRESHOLD_MILLIS; void updateThresholds() { - synchronized (mSamples) { - List<TemperatureThreshold> thresholds = + List<TemperatureThreshold> thresholds = mHalWrapper.getTemperatureThresholds(true, Temperature.TYPE_SKIN); + synchronized (mSamples) { if (Flags.allowThermalHeadroomThresholds()) { Arrays.fill(mHeadroomThresholds, Float.NaN); } diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java index c3db7dd7bfaf..cc6904f9b3af 100644 --- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java +++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java @@ -37,8 +37,7 @@ import android.graphics.Rect; import android.os.SystemProperties; import android.util.Size; import android.view.Gravity; - -import com.android.server.wm.utils.DesktopModeFlagsUtil; +import android.window.flags.DesktopModeFlags; import java.util.function.Consumer; @@ -104,7 +103,7 @@ public final class DesktopModeBoundsCalculator { final TaskDisplayArea displayArea = task.getDisplayArea(); final Rect screenBounds = displayArea.getBounds(); final Size idealSize = calculateIdealSize(screenBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE); - if (!DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(activity.mWmService.mContext)) { + if (!DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(activity.mWmService.mContext)) { return centerInScreen(idealSize, screenBounds); } if (activity.mAppCompatController.getAppCompatAspectRatioOverrides() diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java index e0c0c2c60123..61fbb96882ec 100644 --- a/services/core/java/com/android/server/wm/DesktopModeHelper.java +++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java @@ -19,10 +19,10 @@ package com.android.server.wm; import android.annotation.NonNull; import android.content.Context; import android.os.SystemProperties; +import android.window.flags.DesktopModeFlags; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.wm.utils.DesktopModeFlagsUtil; /** * Constants for desktop mode feature @@ -36,7 +36,7 @@ public final class DesktopModeHelper { /** Whether desktop mode is enabled. */ static boolean isDesktopModeEnabled(@NonNull Context context) { - return DesktopModeFlagsUtil.DESKTOP_WINDOWING_MODE.isEnabled(context); + return DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context); } /** diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp index fe73025e9736..36ea24195789 100644 --- a/services/tests/displayservicetests/Android.bp +++ b/services/tests/displayservicetests/Android.bp @@ -22,6 +22,7 @@ android_test { static_libs: [ "androidx.test.ext.junit", "androidx.test.rules", + "compatibility-device-util-axt", "flag-junit", "frameworks-base-testutils", "junit", @@ -48,6 +49,10 @@ android_test { "automotive-tests", ], + data: [ + ":DisplayManagerTestApp", + ], + certificate: "platform", dxflags: ["--multi-dex"], diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml index 74260cdd497c..37a34eeb9724 100644 --- a/services/tests/displayservicetests/AndroidManifest.xml +++ b/services/tests/displayservicetests/AndroidManifest.xml @@ -39,6 +39,10 @@ android:testOnly="true"> <uses-library android:name="android.test.mock" android:required="true" /> <uses-library android:name="android.test.runner" /> + <activity android:name="com.android.server.display.SimpleActivity" + android:exported="true" /> + <activity android:name="com.android.server.display.SimpleActivity2" + android:exported="true" /> </application> <instrumentation diff --git a/services/tests/displayservicetests/AndroidTest.xml b/services/tests/displayservicetests/AndroidTest.xml index 2985f98417df..f3697bbffd5c 100644 --- a/services/tests/displayservicetests/AndroidTest.xml +++ b/services/tests/displayservicetests/AndroidTest.xml @@ -23,6 +23,13 @@ <option name="test-file-name" value="DisplayServiceTests.apk" /> </target_preparer> + <!-- Load additional APKs onto device --> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="DisplayManagerTestApp.apk" /> + </target_preparer> + <option name="test-tag" value="DisplayServiceTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> <option name="package" value="com.android.frameworks.displayservicetests" /> diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java new file mode 100644 index 000000000000..90f62577b261 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java @@ -0,0 +1,441 @@ +/* + * 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.display; + +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; +import static android.util.DisplayMetrics.DENSITY_HIGH; +import static android.util.DisplayMetrics.DENSITY_MEDIUM; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import android.app.ActivityManager; +import android.app.Instrumentation; +import android.content.Context; +import android.content.Intent; +import android.hardware.display.DisplayManager; +import android.hardware.display.VirtualDisplay; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.platform.test.annotations.AppModeSdkSandbox; +import android.util.Log; +import android.util.SparseArray; + +import androidx.annotation.GuardedBy; +import androidx.annotation.NonNull; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.compatibility.common.util.SystemUtil; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * Tests that applications can receive display events correctly. + */ +@RunWith(Parameterized.class) +@AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).") +public class DisplayEventDeliveryTest { + private static final String TAG = "DisplayEventDeliveryTest"; + + private static final String NAME = TAG; + private static final int WIDTH = 720; + private static final int HEIGHT = 480; + + private static final int MESSAGE_LAUNCHED = 1; + private static final int MESSAGE_CALLBACK = 2; + + private static final int DISPLAY_ADDED = 1; + private static final int DISPLAY_CHANGED = 2; + private static final int DISPLAY_REMOVED = 3; + + private static final long DISPLAY_EVENT_TIMEOUT_MSEC = 100; + private static final long TEST_FAILURE_TIMEOUT_MSEC = 10000; + + private static final String TEST_PACKAGE = + "com.android.servicestests.apps.displaymanagertestapp"; + private static final String TEST_ACTIVITY = TEST_PACKAGE + ".DisplayEventActivity"; + private static final String TEST_DISPLAYS = "DISPLAYS"; + private static final String TEST_MESSENGER = "MESSENGER"; + + private final Object mLock = new Object(); + + private Instrumentation mInstrumentation; + private Context mContext; + private DisplayManager mDisplayManager; + private ActivityManager mActivityManager; + private ActivityManager.OnUidImportanceListener mUidImportanceListener; + private CountDownLatch mLatchActivityLaunch; + private CountDownLatch mLatchActivityCached; + private HandlerThread mHandlerThread; + private Handler mHandler; + private Messenger mMessenger; + private int mPid; + private int mUid; + + /** + * Array of DisplayBundle. The test handler uses it to check if certain display events have + * been sent to DisplayEventActivity. + * Key: displayId of each new VirtualDisplay created by this test + * Value: DisplayBundle, storing the VirtualDisplay and its expected display events + * + * NOTE: The lock is required when adding and removing virtual displays. Otherwise it's not + * necessary to lock mDisplayBundles when accessing it from the test function. + */ + @GuardedBy("mLock") + private SparseArray<DisplayBundle> mDisplayBundles; + + /** + * Helper class to store VirtualDisplay and its corresponding display events expected to be + * sent to DisplayEventActivity. + */ + private static final class DisplayBundle { + private VirtualDisplay mVirtualDisplay; + private final int mDisplayId; + + // Display events we expect to receive before timeout + private final LinkedBlockingQueue<Integer> mExpectations; + + DisplayBundle(VirtualDisplay display) { + mVirtualDisplay = display; + mDisplayId = display.getDisplay().getDisplayId(); + mExpectations = new LinkedBlockingQueue<>(); + } + + public void releaseDisplay() { + if (mVirtualDisplay != null) { + mVirtualDisplay.release(); + } + mVirtualDisplay = null; + } + + /** + * Add the received display event from the test activity to the queue + * + * @param event The corresponding display event + */ + public void addDisplayEvent(int event) { + Log.d(TAG, "Received " + mDisplayId + " " + event); + mExpectations.offer(event); + } + + + /** + * Assert that there isn't any unexpected display event from the test activity + */ + public void assertNoDisplayEvents() { + try { + assertNull(mExpectations.poll(DISPLAY_EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + /** + * Wait for the expected display event from the test activity + * + * @param expect The expected display event + */ + public void waitDisplayEvent(int expect) { + while (true) { + try { + final Integer event; + event = mExpectations.poll(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS); + assertNotNull(event); + if (expect == event) { + Log.d(TAG, "Found " + mDisplayId + " " + event); + return; + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + } + + /** + * How many virtual displays to create during the test + */ + @Parameter(0) + public int mDisplayCount; + + /** + * True if running the test activity in cached mode + * False if running it in non-cached mode + */ + @Parameter(1) + public boolean mCached; + + @Parameters(name = "#{index}: {0} {1}") + public static Iterable<? extends Object> data() { + return Arrays.asList(new Object[][]{ + {1, false}, {2, false}, {3, false}, {10, false}, + {1, true}, {2, true}, {3, true}, {10, true} + }); + } + + private class TestHandler extends Handler { + TestHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(@NonNull Message msg) { + switch (msg.what) { + case MESSAGE_LAUNCHED: + mPid = msg.arg1; + mUid = msg.arg2; + Log.d(TAG, "Launched " + mPid + " " + mUid); + mLatchActivityLaunch.countDown(); + break; + case MESSAGE_CALLBACK: + Log.d(TAG, "Callback " + msg.arg1 + " " + msg.arg2); + synchronized (mLock) { + // arg1: displayId + DisplayBundle bundle = mDisplayBundles.get(msg.arg1); + if (bundle != null) { + // arg2: display event + bundle.addDisplayEvent(msg.arg2); + } + } + break; + default: + fail("Unexpected value: " + msg.what); + break; + } + } + } + + @Before + public void setUp() throws Exception { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mContext = mInstrumentation.getContext(); + mDisplayManager = mContext.getSystemService(DisplayManager.class); + mLatchActivityLaunch = new CountDownLatch(1); + mLatchActivityCached = new CountDownLatch(1); + mActivityManager = mContext.getSystemService(ActivityManager.class); + mUidImportanceListener = (uid, importance) -> { + if (uid == mUid && importance == IMPORTANCE_CACHED) { + Log.d(TAG, "Listener " + uid + " becomes " + importance); + mLatchActivityCached.countDown(); + } + }; + SystemUtil.runWithShellPermissionIdentity(() -> + mActivityManager.addOnUidImportanceListener(mUidImportanceListener, + IMPORTANCE_CACHED)); + // The lock is not functionally necessary but eliminates lint error messages. + synchronized (mLock) { + mDisplayBundles = new SparseArray<>(); + } + mHandlerThread = new HandlerThread("handler"); + mHandlerThread.start(); + mHandler = new TestHandler(mHandlerThread.getLooper()); + mMessenger = new Messenger(mHandler); + mPid = 0; + } + + @After + public void tearDown() throws Exception { + mActivityManager.removeOnUidImportanceListener(mUidImportanceListener); + mHandlerThread.quitSafely(); + synchronized (mLock) { + for (int i = 0; i < mDisplayBundles.size(); i++) { + DisplayBundle bundle = mDisplayBundles.valueAt(i); + // Clean up unreleased virtual display + bundle.releaseDisplay(); + } + mDisplayBundles.clear(); + } + SystemUtil.runShellCommand(mInstrumentation, "am force-stop " + TEST_PACKAGE); + } + + /** + * Return a display bundle at the stated index. The bundle is retrieved under lock. + */ + private DisplayBundle displayBundleAt(int i) { + synchronized (mLock) { + return mDisplayBundles.valueAt(i); + } + } + + /** + * Create virtual displays, change their configurations and release them + * mDisplays: the amount of virtual displays to be created + * mCached: true to run the test activity in cached mode; false in non-cached mode + */ + @Test + public void testDisplayEvents() { + Log.d(TAG, "Start test testDisplayEvents " + mDisplayCount + " " + mCached); + // Launch DisplayEventActivity and start listening to display events + launchTestActivity(); + + if (mCached) { + // The test activity in cached mode won't receive the pending display events + makeTestActivityCached(); + } + + // Create new virtual displays + for (int i = 0; i < mDisplayCount; i++) { + // Lock is needed here to ensure the handler can query the displays + synchronized (mLock) { + VirtualDisplay display = createVirtualDisplay(NAME + i); + DisplayBundle bundle = new DisplayBundle(display); + mDisplayBundles.put(bundle.mDisplayId, bundle); + } + } + + for (int i = 0; i < mDisplayCount; i++) { + if (mCached) { + // DISPLAY_ADDED should be deferred for cached process + displayBundleAt(i).assertNoDisplayEvents(); + } else { + // DISPLAY_ADDED should arrive immediately for non-cached process + displayBundleAt(i).waitDisplayEvent(DISPLAY_ADDED); + } + } + + // Change the virtual displays + for (int i = 0; i < mDisplayCount; i++) { + DisplayBundle bundle = displayBundleAt(i); + bundle.mVirtualDisplay.resize(WIDTH, HEIGHT, DENSITY_HIGH); + } + + for (int i = 0; i < mDisplayCount; i++) { + if (mCached) { + // DISPLAY_CHANGED should be deferred for cached process + displayBundleAt(i).assertNoDisplayEvents(); + } else { + // DISPLAY_CHANGED should arrive immediately for non-cached process + displayBundleAt(i).waitDisplayEvent(DISPLAY_CHANGED); + } + } + + if (mCached) { + // The test activity becomes non-cached and should receive the pending display events + bringTestActivityTop(); + + for (int i = 0; i < mDisplayCount; i++) { + // The pending DISPLAY_ADDED & DISPLAY_CHANGED should arrive now + displayBundleAt(i).waitDisplayEvent(DISPLAY_ADDED); + displayBundleAt(i).waitDisplayEvent(DISPLAY_CHANGED); + } + } + + // Release the virtual displays + for (int i = 0; i < mDisplayCount; i++) { + displayBundleAt(i).releaseDisplay(); + } + + // DISPLAY_REMOVED should arrive now + for (int i = 0; i < mDisplayCount; i++) { + displayBundleAt(i).waitDisplayEvent(DISPLAY_REMOVED); + } + } + + /** + * Launch the test activity that would listen to display events + */ + private void launchTestActivity() { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY); + intent.putExtra(TEST_MESSENGER, mMessenger); + intent.putExtra(TEST_DISPLAYS, mDisplayCount); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + SystemUtil.runWithShellPermissionIdentity( + () -> { + mContext.startActivity(intent); + }, + android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); + waitLatch(mLatchActivityLaunch); + } + + /** + * Bring the test activity back to top + */ + private void bringTestActivityTop() { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + SystemUtil.runWithShellPermissionIdentity( + () -> { + mContext.startActivity(intent); + }, + android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); + } + + /** + * Bring the test activity into cached mode by launching another 2 apps + */ + private void makeTestActivityCached() { + // Launch another activity to bring the test activity into background + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setClass(mContext, SimpleActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + + // Launch another activity to bring the test activity into cached mode + Intent intent2 = new Intent(Intent.ACTION_MAIN); + intent2.setClass(mContext, SimpleActivity2.class); + intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + SystemUtil.runWithShellPermissionIdentity( + () -> { + mInstrumentation.startActivitySync(intent); + mInstrumentation.startActivitySync(intent2); + }, + android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); + waitLatch(mLatchActivityCached); + } + + /** + * Create a virtual display + * + * @param name The name of the new virtual display + * @return The new virtual display + */ + private VirtualDisplay createVirtualDisplay(String name) { + return mDisplayManager.createVirtualDisplay(name, WIDTH, HEIGHT, DENSITY_MEDIUM, + null /* surface: as we don't actually draw anything, null is enough */, + VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY + /* flags: a public virtual display that another app can access */); + } + + /** + * Wait for CountDownLatch with timeout + */ + private void waitLatch(CountDownLatch latch) { + try { + latch.await(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/SimpleActivity.java b/services/tests/displayservicetests/src/com/android/server/display/SimpleActivity.java new file mode 100644 index 000000000000..c4ebbd9fd876 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/SimpleActivity.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.app.Activity; + +/** + * An activity doing nothing + */ +public final class SimpleActivity extends Activity { } diff --git a/services/tests/displayservicetests/src/com/android/server/display/SimpleActivity2.java b/services/tests/displayservicetests/src/com/android/server/display/SimpleActivity2.java new file mode 100644 index 000000000000..a719a57efb2d --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/SimpleActivity2.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.app.Activity; + +/** + * Another activity doing nothing + */ +public final class SimpleActivity2 extends Activity { } diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index e55e0f27dfb4..b41b30cf0e2e 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -30,6 +30,7 @@ android_test { "src/**/*.kt", "test-apps/SuspendTestApp/src/**/*.java", + "test-apps/DisplayManagerTestApp/src/**/*.java", ], kotlincflags: [ @@ -134,6 +135,7 @@ android_test { }, data: [ + ":DisplayManagerTestApp", ":SimpleServiceTestApp1", ":SimpleServiceTestApp2", ":SimpleServiceTestApp3", diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index b56af87ee020..5298251b79f7 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -35,6 +35,7 @@ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> + <option name="test-file-name" value="DisplayManagerTestApp.apk" /> <option name="test-file-name" value="FrameworksServicesTests.apk" /> <option name="test-file-name" value="SuspendTestApp.apk" /> <option name="test-file-name" value="SimpleServiceTestApp1.apk" /> diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index 2ba3969bb9e5..87c9db2fe565 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -92,6 +92,7 @@ public abstract class BaseLockSettingsServiceTests { MockLockSettingsContext mContext; LockSettingsStorageTestable mStorage; + LockSettingsStrongAuth mStrongAuth; Resources mResources; FakeGateKeeperService mGateKeeperService; @@ -135,6 +136,7 @@ public abstract class BaseLockSettingsServiceTests { mFingerprintManager = mock(FingerprintManager.class); mFaceManager = mock(FaceManager.class); mPackageManager = mock(PackageManager.class); + mStrongAuth = mock(LockSettingsStrongAuth.class); LocalServices.removeServiceForTest(LockSettingsInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); @@ -162,7 +164,7 @@ public abstract class BaseLockSettingsServiceTests { mInjector = new LockSettingsServiceTestable.MockInjector( mContext, - mStorage, + mStorage, mStrongAuth, mActivityManager, setUpStorageManagerMock(), mSpManager, diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java index 93fc071a5bb7..abd39b0bb963 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java @@ -50,6 +50,7 @@ public class LockSettingsServiceTestable extends LockSettingsService { public static class MockInjector extends LockSettingsService.Injector { private LockSettingsStorage mLockSettingsStorage; + private final LockSettingsStrongAuth mStrongAuth; private IActivityManager mActivityManager; private IStorageManager mStorageManager; private SyntheticPasswordManager mSpManager; @@ -62,12 +63,14 @@ public class LockSettingsServiceTestable extends LockSettingsService { public boolean mIsMainUserPermanentAdmin = false; public MockInjector(Context context, LockSettingsStorage storage, + LockSettingsStrongAuth strongAuth, IActivityManager activityManager, IStorageManager storageManager, SyntheticPasswordManager spManager, FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager, UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) { super(context); mLockSettingsStorage = storage; + mStrongAuth = strongAuth; mActivityManager = activityManager; mStorageManager = storageManager; mSpManager = spManager; @@ -89,7 +92,7 @@ public class LockSettingsServiceTestable extends LockSettingsService { @Override public LockSettingsStrongAuth getStrongAuth() { - return mock(LockSettingsStrongAuth.class); + return mStrongAuth; } @Override diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java index 601a01624189..2868e559e02f 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java @@ -17,6 +17,7 @@ package com.android.server.locksettings; import static android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION; +import static android.security.Flags.FLAG_CLEAR_STRONG_AUTH_ON_ADD_PRIMARY_CREDENTIAL; import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; @@ -46,6 +47,10 @@ import android.os.UserHandle; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import android.service.gatekeeper.GateKeeperResponse; import android.text.TextUtils; @@ -71,6 +76,8 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Before public void setUp() { @@ -258,6 +265,34 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { } @Test + @RequiresFlagsEnabled(FLAG_CLEAR_STRONG_AUTH_ON_ADD_PRIMARY_CREDENTIAL) + public void setLockCredential_forPrimaryUser_clearsStrongAuthWhenFlagIsOn() + throws Exception { + setCredential(PRIMARY_USER_ID, newPassword("password")); + + verify(mStrongAuth).reportUnlock(PRIMARY_USER_ID); + } + + @Test + @RequiresFlagsDisabled(FLAG_CLEAR_STRONG_AUTH_ON_ADD_PRIMARY_CREDENTIAL) + public void setLockCredential_forPrimaryUser_leavesStrongAuthWhenFlagIsOff() + throws Exception { + setCredential(PRIMARY_USER_ID, newPassword("password")); + + verify(mStrongAuth, never()).reportUnlock(anyInt()); + } + + @Test + public void setLockCredential_forPrimaryUserWithCredential_leavesStrongAuth() throws Exception { + setCredential(PRIMARY_USER_ID, newPassword("password")); + reset(mStrongAuth); + + setCredential(PRIMARY_USER_ID, newPassword("password2"), newPassword("password")); + + verify(mStrongAuth, never()).reportUnlock(anyInt()); + } + + @Test public void testSetLockCredential_forProfileWithSeparateChallenge_sendsCredentials() throws Exception { setCredential(MANAGED_PROFILE_USER_ID, newPattern("12345")); @@ -278,6 +313,28 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { } @Test + @RequiresFlagsEnabled(FLAG_CLEAR_STRONG_AUTH_ON_ADD_PRIMARY_CREDENTIAL) + public void setLockCredential_profileWithNewSeparateChallenge_clearsStrongAuthWhenFlagIsOn() + throws Exception { + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, true, null); + + setCredential(MANAGED_PROFILE_USER_ID, newPattern("12345")); + + verify(mStrongAuth).reportUnlock(MANAGED_PROFILE_USER_ID); + } + + @Test + @RequiresFlagsDisabled(FLAG_CLEAR_STRONG_AUTH_ON_ADD_PRIMARY_CREDENTIAL) + public void setLockCredential_profileWithNewSeparateChallenge_leavesStrongAuthWhenFlagIsOff() + throws Exception { + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, true, null); + + setCredential(MANAGED_PROFILE_USER_ID, newPattern("12345")); + + verify(mStrongAuth, never()).reportUnlock(anyInt()); + } + + @Test public void testSetLockCredential_forProfileWithUnifiedChallenge_doesNotSendRandomCredential() throws Exception { mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); @@ -305,6 +362,67 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { MANAGED_PROFILE_USER_ID); } + + @Test + public void setLockCredential_primaryWithUnifiedProfileAndCredential_leavesStrongAuthForBoth() + throws Exception { + final LockscreenCredential oldCredential = newPassword("oldPassword"); + final LockscreenCredential newCredential = newPassword("newPassword"); + setCredential(PRIMARY_USER_ID, oldCredential); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + reset(mStrongAuth); + + setCredential(PRIMARY_USER_ID, newCredential, oldCredential); + + verify(mStrongAuth, never()).reportUnlock(anyInt()); + } + + @Test + @RequiresFlagsEnabled(FLAG_CLEAR_STRONG_AUTH_ON_ADD_PRIMARY_CREDENTIAL) + public void setLockCredential_primaryWithUnifiedProfile_clearsStrongAuthForBothWhenFlagIsOn() + throws Exception { + final LockscreenCredential credential = newPassword("oldPassword"); + setCredential(PRIMARY_USER_ID, credential); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + clearCredential(PRIMARY_USER_ID, credential); + reset(mStrongAuth); + + setCredential(PRIMARY_USER_ID, credential); + + verify(mStrongAuth).reportUnlock(PRIMARY_USER_ID); + verify(mStrongAuth).reportUnlock(MANAGED_PROFILE_USER_ID); + } + + @Test + @RequiresFlagsDisabled(FLAG_CLEAR_STRONG_AUTH_ON_ADD_PRIMARY_CREDENTIAL) + public void setLockCredential_primaryWithUnifiedProfile_leavesStrongAuthForBothWhenFlagIsOff() + throws Exception { + final LockscreenCredential credential = newPassword("oldPassword"); + setCredential(PRIMARY_USER_ID, credential); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + clearCredential(PRIMARY_USER_ID, credential); + reset(mStrongAuth); + + setCredential(PRIMARY_USER_ID, credential); + + verify(mStrongAuth, never()).reportUnlock(anyInt()); + } + + + @Test + public void setLockCredential_primaryWithUnifiedProfileWithCredential_leavesStrongAuthForBoth() + throws Exception { + final LockscreenCredential oldCredential = newPassword("oldPassword"); + final LockscreenCredential newCredential = newPassword("newPassword"); + setCredential(PRIMARY_USER_ID, oldCredential); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + reset(mStrongAuth); + + setCredential(PRIMARY_USER_ID, newCredential, oldCredential); + + verify(mStrongAuth, never()).reportUnlock(anyInt()); + } + @Test public void testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_removesBothCredentials() @@ -343,6 +461,18 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { } @Test + public void clearLockCredential_primaryWithUnifiedProfile_leavesStrongAuthForBoth() + throws Exception { + setCredential(PRIMARY_USER_ID, newPassword("password")); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + reset(mStrongAuth); + + clearCredential(PRIMARY_USER_ID, newPassword("password")); + + verify(mStrongAuth, never()).reportUnlock(anyInt()); + } + + @Test public void testSetLockCredential_forUnifiedToSeparateChallengeProfile_sendsNewCredentials() throws Exception { final LockscreenCredential parentPassword = newPassword("parentPassword"); diff --git a/services/tests/servicestests/test-apps/DisplayManagerTestApp/Android.bp b/services/tests/servicestests/test-apps/DisplayManagerTestApp/Android.bp new file mode 100644 index 000000000000..962ae9be4103 --- /dev/null +++ b/services/tests/servicestests/test-apps/DisplayManagerTestApp/Android.bp @@ -0,0 +1,37 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "DisplayManagerTestApp", + + sdk_version: "current", + + srcs: ["**/*.java"], + + dex_preopt: { + enabled: false, + }, + optimize: { + enabled: false, + }, +} diff --git a/services/tests/servicestests/test-apps/DisplayManagerTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/DisplayManagerTestApp/AndroidManifest.xml new file mode 100644 index 000000000000..c0d9d6fd3719 --- /dev/null +++ b/services/tests/servicestests/test-apps/DisplayManagerTestApp/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.servicestests.apps.displaymanagertestapp"> + + <application android:label="DisplayEventTestApp"> + <activity android:name=".DisplayEventActivity" + android:exported="true" /> + </application> + +</manifest> diff --git a/services/tests/servicestests/test-apps/DisplayManagerTestApp/OWNERS b/services/tests/servicestests/test-apps/DisplayManagerTestApp/OWNERS new file mode 100644 index 000000000000..e9557f84f8fb --- /dev/null +++ b/services/tests/servicestests/test-apps/DisplayManagerTestApp/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 345010 + +include /services/core/java/com/android/server/display/OWNERS diff --git a/services/tests/servicestests/test-apps/DisplayManagerTestApp/src/com/android/servicestests/apps/displaymanagertestapp/DisplayEventActivity.java b/services/tests/servicestests/test-apps/DisplayManagerTestApp/src/com/android/servicestests/apps/displaymanagertestapp/DisplayEventActivity.java new file mode 100644 index 000000000000..07754b201758 --- /dev/null +++ b/services/tests/servicestests/test-apps/DisplayManagerTestApp/src/com/android/servicestests/apps/displaymanagertestapp/DisplayEventActivity.java @@ -0,0 +1,119 @@ +/* + * 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.servicestests.apps.displaymanagertestapp; + +import android.app.Activity; +import android.content.Intent; +import android.hardware.display.DisplayManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; + +/** + * A simple activity manipulating displays and listening to corresponding display events + */ +public class DisplayEventActivity extends Activity { + private static final String TAG = DisplayEventActivity.class.getSimpleName(); + + private static final String TEST_DISPLAYS = "DISPLAYS"; + private static final String TEST_MESSENGER = "MESSENGER"; + + private static final int MESSAGE_LAUNCHED = 1; + private static final int MESSAGE_CALLBACK = 2; + + private static final int DISPLAY_ADDED = 1; + private static final int DISPLAY_CHANGED = 2; + private static final int DISPLAY_REMOVED = 3; + + private int mExpectedDisplayCount; + private int mSeenDisplayCount; + private Messenger mMessenger; + private DisplayManager mDisplayManager; + private DisplayManager.DisplayListener mDisplayListener; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent intent = getIntent(); + mExpectedDisplayCount = 0; + mSeenDisplayCount = intent.getIntExtra(TEST_DISPLAYS, 0); + mMessenger = intent.getParcelableExtra(TEST_MESSENGER, Messenger.class); + mDisplayManager = getApplicationContext().getSystemService(DisplayManager.class); + mDisplayListener = new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + callback(displayId, DISPLAY_ADDED); + } + + @Override + public void onDisplayRemoved(int displayId) { + callback(displayId, DISPLAY_REMOVED); + } + + @Override + public void onDisplayChanged(int displayId) { + callback(displayId, DISPLAY_CHANGED); + } + }; + Handler handler = new Handler(Looper.getMainLooper()); + mDisplayManager.registerDisplayListener(mDisplayListener, handler); + launched(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mDisplayManager.unregisterDisplayListener(mDisplayListener); + } + + private void launched() { + try { + Message msg = Message.obtain(); + msg.what = MESSAGE_LAUNCHED; + msg.arg1 = Process.myPid(); + msg.arg2 = Process.myUid(); + Log.d(TAG, "Launched " + mSeenDisplayCount); + mMessenger.send(msg); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + private void callback(int displayId, int event) { + try { + Message msg = Message.obtain(); + msg.what = MESSAGE_CALLBACK; + msg.arg1 = displayId; + msg.arg2 = event; + Log.d(TAG, "Msg " + msg.arg1 + " " + msg.arg2); + mMessenger.send(msg); + if (event == DISPLAY_REMOVED) { + mExpectedDisplayCount++; + if (mExpectedDisplayCount >= mSeenDisplayCount) { + finish(); + } + } + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 196bc47572ba..96ddf8079e17 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -99,6 +99,7 @@ import static android.service.notification.Adjustment.TYPE_NEWS; import static android.service.notification.Condition.SOURCE_CONTEXT; import static android.service.notification.Condition.SOURCE_USER_ACTION; import static android.service.notification.Condition.STATE_TRUE; +import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION; import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING; import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; @@ -4412,7 +4413,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { eq(mTestNotificationChannel.getId()), anyBoolean())) .thenReturn(mTestNotificationChannel); when(mPreferencesHelper.deleteNotificationChannel(eq(mPkg), anyInt(), - eq(mTestNotificationChannel.getId()), anyInt(), anyBoolean())).thenReturn(true); + eq(mTestNotificationChannel.getId()), anyInt(), anyBoolean())).thenReturn(true); reset(mListeners); mBinderService.deleteNotificationChannel(mPkg, mTestNotificationChannel.getId()); verify(mListeners, times(1)).notifyNotificationChannelChanged(eq(mPkg), @@ -4421,6 +4422,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) + public void testAppsCannotDeleteBundleChannel() throws Exception { + when(mCompanionMgr.getAssociations(mPkg, mUserId)) + .thenReturn(singletonList(mock(AssociationInfo.class))); + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.getNotificationChannel(eq(mPkg), anyInt(), + eq(NEWS_ID), anyBoolean())) + .thenReturn(mTestNotificationChannel); + when(mPreferencesHelper.deleteNotificationChannel(eq(mPkg), anyInt(), + eq(NEWS_ID), anyInt(), anyBoolean())).thenReturn(true); + reset(mListeners); + mBinderService.deleteNotificationChannel(mPkg, NEWS_ID); + verify(mListeners, never()).notifyNotificationChannelChanged(eq(mPkg), + eq(Process.myUserHandle()), any(), + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED)); + } + + @Test public void testDeleteChannelOnlyDoExtraWorkIfExisted() throws Exception { when(mCompanionMgr.getAssociations(mPkg, mUserId)) .thenReturn(singletonList(mock(AssociationInfo.class))); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 559c32413d70..1905ae4aec4b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -6222,6 +6222,47 @@ public class PreferencesHelperTest extends UiServiceTestCase { .isEqualTo(IMPORTANCE_LOW); } + @Test + @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) + public void testNotificationBundles_appsCannotUpdate() { + // do something that triggers settings creation for an app + mHelper.setShowBadge(PKG_O, UID_O, true); + + NotificationChannel fromApp = + new NotificationChannel(NEWS_ID, "The best channel", IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_O, UID_O, fromApp, true, false, UID_O, false); + + assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, NEWS_ID, false).getImportance()) + .isEqualTo(IMPORTANCE_LOW); + } + + @Test + @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) + public void testNotificationBundles_osCanAllowToBypassDnd() { + // do something that triggers settings creation for an app + mHelper.setShowBadge(PKG_O, UID_O, true); + + NotificationChannel fromApp = + new NotificationChannel(NEWS_ID, "The best channel", IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_O, UID_O, fromApp, true, false, UID_O, false); + } + + @Test + @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) + public void testUnDeleteBundleChannelsOnLoadIfNotUserChange() throws Exception { + mHelper.setShowBadge(PKG_P, UID_P, true); + // the public create/update methods should prevent this, so take advantage of the fact that + // the object is in the same process + mHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true).setDeleted(true); + + ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false, + UserHandle.USER_ALL, SOCIAL_MEDIA_ID); + + loadStreamXml(baos, false, UserHandle.USER_ALL); + + assertThat(mXmlHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true). + isDeleted()).isFalse(); + } @Test public void testRestoredWithoutUid_threadSafety() throws Exception { diff --git a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl index 579fda320e9a..a0f01bded764 100644 --- a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl +++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl @@ -50,4 +50,11 @@ oneway interface ISatelliteTransmissionUpdateCallback { * Satellite location is based on magnetic north direction. */ void onSatellitePositionChanged(in PointingInfo pointingInfo); + + /** + * Called when framework receives a request to send a datagram. + * + * @param datagramType The type of the requested datagram. + */ + void onSendDatagramRequested(int datagramType); } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 284e2bd8aa6c..90dae3be058c 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -1220,6 +1220,12 @@ public final class SatelliteManager { () -> callback.onReceiveDatagramStateChanged( state, receivePendingCount, errorCode))); } + + @Override + public void onSendDatagramRequested(int datagramType) { + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> callback.onSendDatagramRequested(datagramType))); + } }; sSatelliteTransmissionUpdateCallbackMap.put(callback, internalCallback); telephony.startSatelliteTransmissionUpdates(errorCallback, internalCallback); diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java index d8bd66284fb0..046ae5fdeb3c 100644 --- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java @@ -75,4 +75,13 @@ public interface SatelliteTransmissionUpdateCallback { void onReceiveDatagramStateChanged( @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount, @SatelliteManager.SatelliteResult int errorCode); + + /** + * Called when framework receives a request to send a datagram. + * + * @param datagramType The type of the requested datagram. + * + * @hide + */ + default void onSendDatagramRequested(@SatelliteManager.DatagramType int datagramType) {} } diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp index ea77b8de97aa..4920f7b41e3f 100644 --- a/tools/hoststubgen/hoststubgen/Android.bp +++ b/tools/hoststubgen/hoststubgen/Android.bp @@ -5,6 +5,10 @@ package { // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 default_applicable_licenses: ["frameworks_base_license"], + + // OWNER: g/ravenwood + // Bug component: 25698 + default_team: "trendy_team_framework_backstage_power", } // Visibility only for ravenwood prototype uses. |