diff options
8 files changed, 291 insertions, 25 deletions
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java index 371161ebd..711594386 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -18,16 +18,11 @@ package com.android.launcher3; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; import static com.android.launcher3.LauncherState.NORMAL; -import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator; -import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS; -import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY; -import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -35,18 +30,17 @@ import android.animation.ObjectAnimator; import android.content.Context; import android.view.View; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.anim.SpringObjectAnimator; +import com.android.launcher3.anim.SpringAnimationBuilder; import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + /** * A {@link QuickstepAppTransitionManagerImpl} that also implements recents transitions from * {@link RecentsView}. @@ -156,8 +150,11 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(), RecentsView.CONTENT_ALPHA, values); case INDEX_RECENTS_TRANSLATE_X_ANIM: - return new SpringObjectAnimator<>(mLauncher.getOverviewPanel(), - VIEW_TRANSLATE_X, MIN_VISIBLE_CHANGE_PIXELS, 0.8f, 250, values); + return new SpringAnimationBuilder<>(mLauncher.getOverviewPanel(), VIEW_TRANSLATE_X) + .setDampingRatio(0.8f) + .setStiffness(250) + .setValues(values) + .build(mLauncher); default: return super.createStateElementAnimation(index, values); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java b/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java index e42508814..853a1c6ac 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java +++ b/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java @@ -16,17 +16,34 @@ package com.android.launcher3.uioverrides; +import android.content.Context; import android.provider.DeviceConfig; import com.android.launcher3.config.BaseFlags.BaseTogglableFlag; public class TogglableFlag extends BaseTogglableFlag { + public static final String NAMESPACE_LAUNCHER = "launcher"; + public static final String TAG = "TogglableFlag"; public TogglableFlag(String key, boolean defaultValue, String description) { super(key, defaultValue, description); } @Override - public boolean getInitialValue(boolean value) { - return DeviceConfig.getBoolean("launcher", getKey(), value); + public boolean getOverridenDefaultValue(boolean value) { + return DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, getKey(), value); + } + + @Override + public void addChangeListener(Context context, Runnable r) { + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_LAUNCHER, + context.getMainExecutor(), + (properties) -> { + if (!NAMESPACE_LAUNCHER.equals(properties.getNamespace())) { + return; + } + initialize(context); + r.run(); + }); } } diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 2a801d6ef..b4a2216c5 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -94,6 +94,7 @@ public class LauncherAppState { if (FeatureFlags.IS_DOGFOOD_BUILD) { filter.addAction(ACTION_FORCE_ROLOAD); } + FeatureFlags.APP_SEARCH_IMPROVEMENTS.addChangeListener(context, mModel::forceReload); mContext.registerReceiver(mModel, filter); UserManagerCompat.getInstance(mContext).enableAndResetCache(); diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java new file mode 100644 index 000000000..0f34c1e97 --- /dev/null +++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2019 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.launcher3.anim; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.util.FloatProperty; + +import com.android.launcher3.util.DefaultDisplay; + +import androidx.annotation.FloatRange; +import androidx.dynamicanimation.animation.SpringForce; + +/** + * Utility class to build an object animator which follows the same path as a spring animation for + * an underdamped spring. + */ +public class SpringAnimationBuilder<T> extends FloatProperty<T> { + + private final T mTarget; + private final FloatProperty<T> mProperty; + + private float mStartValue; + private float mEndValue; + private float mVelocity = 0; + + private float mStiffness = SpringForce.STIFFNESS_MEDIUM; + private float mDampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY; + private float mMinVisibleChange = 1; + + // Multiplier to the min visible change value for value threshold + private static final float THRESHOLD_MULTIPLIER = 0.65f; + + /** + * The spring equation is given as + * x = e^(-beta*t/2) * (a cos(gamma * t) + b sin(gamma * t) + * v = e^(-beta*t/2) * ((2 * a * gamma + beta * b) * sin(gamma * t) + * + (a * beta - 2 * b * gamma) * cos(gamma * t)) / 2 + * + * a = x(0) + * b = beta * x(0) / (2 * gamma) + v(0) / gamma + */ + private double beta; + private double gamma; + + private double a, b; + private double va, vb; + + // Threshold for velocity and value to determine when it's reasonable to assume that the spring + // is approximately at rest. + private double mValueThreshold; + private double mVelocityThreshold; + + private float mCurrentTime = 0; + + public SpringAnimationBuilder(T target, FloatProperty<T> property) { + super("dynamic-spring-property"); + mTarget = target; + mProperty = property; + + mStartValue = mProperty.get(target); + } + + public SpringAnimationBuilder<T> setEndValue(float value) { + mEndValue = value; + return this; + } + + public SpringAnimationBuilder<T> setStartValue(float value) { + mStartValue = value; + return this; + } + + public SpringAnimationBuilder<T> setValues(float... values) { + if (values.length > 1) { + mStartValue = values[0]; + mEndValue = values[values.length - 1]; + } else { + mEndValue = values[0]; + } + return this; + } + + public SpringAnimationBuilder<T> setStiffness( + @FloatRange(from = 0.0, fromInclusive = false) float stiffness) { + if (stiffness <= 0) { + throw new IllegalArgumentException("Spring stiffness constant must be positive."); + } + mStiffness = stiffness; + return this; + } + + public SpringAnimationBuilder<T> setDampingRatio( + @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false) + float dampingRatio) { + if (dampingRatio <= 0 || dampingRatio >= 1) { + throw new IllegalArgumentException("Damping ratio must be between 0 and 1"); + } + mDampingRatio = dampingRatio; + return this; + } + + public SpringAnimationBuilder<T> setMinimumVisibleChange( + @FloatRange(from = 0.0, fromInclusive = false) float minimumVisibleChange) { + if (minimumVisibleChange <= 0) { + throw new IllegalArgumentException("Minimum visible change must be positive."); + } + mMinVisibleChange = minimumVisibleChange; + return this; + } + + public SpringAnimationBuilder<T> setStartVelocity(float startVelocity) { + mVelocity = startVelocity; + return this; + } + + @Override + public void setValue(T object, float time) { + mCurrentTime = time; + mProperty.setValue( + object, (float) (exponentialComponent(time) * cosSinX(time)) + mEndValue); + } + + @Override + public Float get(T t) { + return mCurrentTime; + } + + public ObjectAnimator build(Context context) { + int singleFrameMs = DefaultDisplay.getSingleFrameMs(context); + double naturalFreq = Math.sqrt(mStiffness); + double dampedFreq = naturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio); + + // All the calculations assume the stable position to be 0, shift the values accordingly. + beta = 2 * mDampingRatio * naturalFreq; + gamma = dampedFreq; + a = mStartValue - mEndValue; + b = beta * a / (2 * gamma) + mVelocity / gamma; + + va = a * beta / 2 - b * gamma; + vb = a * gamma + beta * b / 2; + + mValueThreshold = mMinVisibleChange * THRESHOLD_MULTIPLIER; + + // This multiplier is used to calculate the velocity threshold given a certain value + // threshold. The idea is that if it takes >= 1 frame to move the value threshold amount, + // then the velocity is a reasonable threshold. + mVelocityThreshold = mValueThreshold * 1000.0 / singleFrameMs; + + // Find the duration (in seconds) for the spring to reach equilibrium. + // equilibrium is reached when x = 0 + double duration = Math.atan2(-a, b) / gamma; + + // Keep moving ahead until the velocity reaches equilibrium. + double piByG = Math.PI / gamma; + while (duration < 0 || Math.abs(exponentialComponent(duration) * cosSinV(duration)) + >= mVelocityThreshold) { + duration += piByG; + } + + // Find the shortest time + double edgeTime = Math.max(0, duration - piByG / 2); + double minDiff = singleFrameMs / 2000.0; // Half frame time in seconds + + do { + if ((duration - edgeTime) < minDiff) { + break; + } + double mid = (edgeTime + duration) / 2; + if (isAtEquilibrium(mid)) { + duration = mid; + } else { + edgeTime = mid; + } + } while (true); + + + long durationMs = (long) (1000.0 * duration); + ObjectAnimator animator = ObjectAnimator.ofFloat(mTarget, this, 0, (float) duration); + animator.setDuration(durationMs).setInterpolator(Interpolators.LINEAR); + animator.addListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + mProperty.setValue(mTarget, mEndValue); + } + }); + return animator; + } + + private boolean isAtEquilibrium(double t) { + double ec = exponentialComponent(t); + + if (Math.abs(ec * cosSinX(t)) >= mValueThreshold) { + return false; + } + return Math.abs(ec * cosSinV(t)) < mVelocityThreshold; + } + + private double exponentialComponent(double t) { + return Math.pow(Math.E, - beta * t / 2); + } + + private double cosSinX(double t) { + return cosSin(t, a, b); + } + + private double cosSinV(double t) { + return cosSin(t, va, vb); + } + + private double cosSin(double t, double cosFactor, double sinFactor) { + double angle = t * gamma; + return cosFactor * Math.cos(angle) + sinFactor * Math.sin(angle); + } +} diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index ea6261a6a..025087b9d 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -143,6 +143,8 @@ public abstract class BaseFlags { public static abstract class BaseTogglableFlag { private final String key; + // should be value that is hardcoded in client side. + // Comparatively, getDefaultValue() can be overridden. private final boolean defaultValue; private final String description; private boolean currentValue; @@ -152,8 +154,9 @@ public abstract class BaseFlags { boolean defaultValue, String description) { this.key = checkNotNull(key); - this.currentValue = this.defaultValue = getInitialValue(defaultValue); + this.currentValue = this.defaultValue = defaultValue; this.description = checkNotNull(description); + synchronized (sLock) { sFlags.add((TogglableFlag)this); } @@ -169,16 +172,18 @@ public abstract class BaseFlags { return key; } - void initialize(Context context) { - currentValue = getFromStorage(context, defaultValue); + protected void initialize(Context context) { + currentValue = getFromStorage(context, getDefaultValue()); } - protected abstract boolean getInitialValue(boolean value); + protected abstract boolean getOverridenDefaultValue(boolean value); + + protected abstract void addChangeListener(Context context, Runnable r); public void updateStorage(Context context, boolean value) { SharedPreferences.Editor editor = context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE).edit(); - if (value == defaultValue) { + if (value == getDefaultValue()) { editor.remove(key).apply(); } else { editor.putBoolean(key, value).apply(); @@ -187,11 +192,11 @@ public abstract class BaseFlags { boolean getFromStorage(Context context, boolean defaultValue) { return context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE) - .getBoolean(key, defaultValue); + .getBoolean(key, getDefaultValue()); } boolean getDefaultValue() { - return defaultValue; + return getOverridenDefaultValue(defaultValue); } /** Returns the value of the flag at process start, including any overrides present. */ @@ -208,6 +213,8 @@ public abstract class BaseFlags { return "TogglableFlag{" + "key=" + key + ", " + "defaultValue=" + defaultValue + ", " + + "overriddenDefaultValue=" + getOverridenDefaultValue(defaultValue) + ", " + + "currentValue=" + currentValue + ", " + "description=" + description + "}"; } @@ -220,7 +227,7 @@ public abstract class BaseFlags { if (o instanceof TogglableFlag) { BaseTogglableFlag that = (BaseTogglableFlag) o; return (this.key.equals(that.getKey())) - && (this.defaultValue == that.getDefaultValue()) + && (this.getDefaultValue() == that.getDefaultValue()) && (this.description.equals(that.getDescription())); } return false; @@ -232,7 +239,7 @@ public abstract class BaseFlags { h$ *= 1000003; h$ ^= key.hashCode(); h$ *= 1000003; - h$ ^= defaultValue ? 1231 : 1237; + h$ ^= getDefaultValue() ? 1231 : 1237; h$ *= 1000003; h$ ^= description.hashCode(); return h$; diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index 55d58b9c0..abff237e8 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -42,6 +42,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic; import com.android.launcher3.icons.cache.BaseIconCache; import com.android.launcher3.icons.cache.CachingLogic; @@ -237,7 +238,8 @@ public class IconCache extends BaseIconCache { @Override protected String getIconSystemState(String packageName) { - return mIconProvider.getSystemStateForPackage(mSystemState, packageName); + return mIconProvider.getSystemStateForPackage(mSystemState, packageName) + + ",flags_asi:" + FeatureFlags.APP_SEARCH_IMPROVEMENTS.get(); } public static abstract class IconLoadRequest extends HandlerRunnable { diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java b/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java index e875a3c46..60f12d82a 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java @@ -16,6 +16,7 @@ package com.android.launcher3.uioverrides; +import android.content.Context; import com.android.launcher3.config.BaseFlags.BaseTogglableFlag; public class TogglableFlag extends BaseTogglableFlag { @@ -25,7 +26,10 @@ public class TogglableFlag extends BaseTogglableFlag { } @Override - public boolean getInitialValue(boolean value) { + public boolean getOverridenDefaultValue(boolean value) { return value; } + + @Override + public void addChangeListener(Context context, Runnable r) { } } diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java index 1c99f3198..cb586aceb 100644 --- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java @@ -15,6 +15,9 @@ */ package com.android.launcher3.ui.widget; +import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName; + +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import androidx.test.filters.LargeTest; @@ -22,6 +25,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.tapl.Widget; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.TestViewHelpers; import com.android.launcher3.util.rule.ShellCommandRule; @@ -59,5 +63,10 @@ public class AddWidgetTest extends AbstractLauncherUiTest { (info, view) -> info instanceof LauncherAppWidgetInfo && ((LauncherAppWidgetInfo) info).providerName.getClassName().equals( widgetInfo.provider.getClassName())).call()); + + final Widget widget = mLauncher.getWorkspace().tryGetWidget(widgetInfo.label, + DEFAULT_UI_TIMEOUT); + assertNotNull("Widget not found on the workspace", widget); + widget.launch(getAppPackageName()); } } |