From 7b97eebbe5bba3c46756ed71064f16f3b532b0f5 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Fri, 8 Nov 2019 13:43:58 -0800 Subject: [PATCH 01/24] Adding utility class to load customization resources for easier prototype Also adding support for grouping multiple plugins in same APK using process name To customize resources update the defination of dymanic_resources in config.xml: @color/delete_target_hover_tint @integer/config_folderDelay @dimen/all_apps_action_spacing @fraction/container_margin Change-Id: I79c08845464510af96b16d375b424ad914657f39 --- res/values-land/dimens.xml | 3 - res/values/config.xml | 3 + res/xml/dynamic_resources.xml | 9 ++ .../settings/DeveloperOptionsFragment.java | 112 ++++++++++-------- .../launcher3/util/DynamicResource.java | 89 ++++++++++++++ .../systemui/plugins/ResourceProvider.java | 47 ++++++++ 6 files changed, 209 insertions(+), 54 deletions(-) create mode 100644 res/xml/dynamic_resources.xml create mode 100644 src/com/android/launcher3/util/DynamicResource.java create mode 100644 src_plugins/com/android/systemui/plugins/ResourceProvider.java diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml index bc658e4830..662b86ebbe 100644 --- a/res/values-land/dimens.xml +++ b/res/values-land/dimens.xml @@ -15,9 +15,6 @@ --> - - 12% - 58dp 48dp diff --git a/res/values/config.xml b/res/values/config.xml index 2a1f6f7864..0dfed97ce3 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -114,4 +114,7 @@ + + + diff --git a/res/xml/dynamic_resources.xml b/res/xml/dynamic_resources.xml new file mode 100644 index 0000000000..f5d262868c --- /dev/null +++ b/res/xml/dynamic_resources.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java index a9242f94e0..3668313bdc 100644 --- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java +++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.settings; +import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; + import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED; import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey; @@ -24,28 +26,21 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.Settings; import android.util.ArrayMap; -import android.util.ArraySet; +import android.util.Pair; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import com.android.launcher3.R; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.config.FlagTogglerPrefUi; -import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; - -import java.util.List; -import java.util.Set; - import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceDataStore; @@ -54,6 +49,16 @@ import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; import androidx.preference.SwitchPreference; +import com.android.launcher3.R; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.config.FlagTogglerPrefUi; +import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + /** * Dev-build only UI allowing developers to toggle flag settings and plugins. * See {@link FeatureFlags}. @@ -154,44 +159,53 @@ public class DeveloperOptionsFragment extends PreferenceFragment { PackageManager pm = getContext().getPackageManager(); Set pluginActions = manager.getPluginActions(); - ArrayMap> plugins = new ArrayMap<>(); + + ArrayMap, ArrayList>> plugins = + new ArrayMap<>(); + + Set pluginPermissionApps = pm.getPackagesHoldingPermissions( + new String[]{PLUGIN_PERMISSION}, MATCH_DISABLED_COMPONENTS) + .stream() + .map(pi -> pi.packageName) + .collect(Collectors.toSet()); + for (String action : pluginActions) { String name = toName(action); List result = pm.queryIntentServices( - new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS); + new Intent(action), MATCH_DISABLED_COMPONENTS); for (ResolveInfo info : result) { String packageName = info.serviceInfo.packageName; - if (!plugins.containsKey(packageName)) { - plugins.put(packageName, new ArraySet<>()); + if (!pluginPermissionApps.contains(packageName)) { + continue; } - plugins.get(packageName).add(name); + + Pair key = Pair.create(packageName, info.serviceInfo.processName); + if (!plugins.containsKey(key)) { + plugins.put(key, new ArrayList<>()); + } + plugins.get(key).add(Pair.create(name, info.serviceInfo)); } } - List apps = pm.getPackagesHoldingPermissions(new String[]{PLUGIN_PERMISSION}, - PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES); - PreferenceDataStore enabled = manager.getPluginEnabler(); - apps.forEach(app -> { - if (!plugins.containsKey(app.packageName)) return; - SwitchPreference pref = new PluginPreference(prefContext, app, enabled); - pref.setSummary("Plugins: " + toString(plugins.get(app.packageName))); - mPluginsCategory.addPreference(pref); + PreferenceDataStore enabler = manager.getPluginEnabler(); + plugins.forEach((key, si) -> { + String packageName = key.first; + List componentNames = si.stream() + .map(p -> new ComponentName(packageName, p.second.name)) + .collect(Collectors.toList()); + if (!componentNames.isEmpty()) { + SwitchPreference pref = new PluginPreference( + prefContext, si.get(0).second.applicationInfo, enabler, componentNames); + pref.setSummary("Plugins: " + + si.stream().map(p -> p.first).collect(Collectors.joining(", "))); + mPluginsCategory.addPreference(pref); + } }); } - private String toString(ArraySet plugins) { - StringBuilder b = new StringBuilder(); - for (String string : plugins) { - if (b.length() != 0) { - b.append(", "); - } - b.append(string); - } - return b.toString(); - } - private String toName(String action) { - String str = action.replace("com.android.systemui.action.PLUGIN_", ""); + String str = action.replace("com.android.systemui.action.PLUGIN_", "") + .replace("com.android.launcher3.action.PLUGIN_", ""); StringBuilder b = new StringBuilder(); for (String s : str.split("_")) { if (b.length() != 0) { @@ -205,18 +219,20 @@ public class DeveloperOptionsFragment extends PreferenceFragment { private static class PluginPreference extends SwitchPreference { private final boolean mHasSettings; - private final PackageInfo mInfo; private final PreferenceDataStore mPluginEnabler; + private final String mPackageName; + private final List mComponentNames; - public PluginPreference(Context prefContext, PackageInfo info, - PreferenceDataStore pluginEnabler) { + PluginPreference(Context prefContext, ApplicationInfo info, + PreferenceDataStore pluginEnabler, List componentNames) { super(prefContext); PackageManager pm = prefContext.getPackageManager(); mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS) .setPackage(info.packageName), 0) != null; - mInfo = info; + mPackageName = info.packageName; + mComponentNames = componentNames; mPluginEnabler = pluginEnabler; - setTitle(info.applicationInfo.loadLabel(pm)); + setTitle(info.loadLabel(pm)); setChecked(isPluginEnabled()); setWidgetLayoutResource(R.layout.switch_preference_with_settings); } @@ -227,9 +243,7 @@ public class DeveloperOptionsFragment extends PreferenceFragment { } private boolean isPluginEnabled() { - for (int i = 0; i < mInfo.services.length; i++) { - ComponentName componentName = new ComponentName(mInfo.packageName, - mInfo.services[i].name); + for (ComponentName componentName : mComponentNames) { if (!isEnabled(componentName)) { return false; } @@ -240,17 +254,14 @@ public class DeveloperOptionsFragment extends PreferenceFragment { @Override protected boolean persistBoolean(boolean isEnabled) { boolean shouldSendBroadcast = false; - for (int i = 0; i < mInfo.services.length; i++) { - ComponentName componentName = new ComponentName(mInfo.packageName, - mInfo.services[i].name); - + for (ComponentName componentName : mComponentNames) { if (isEnabled(componentName) != isEnabled) { mPluginEnabler.putBoolean(pluginEnabledKey(componentName), isEnabled); shouldSendBroadcast = true; } } if (shouldSendBroadcast) { - final String pkg = mInfo.packageName; + final String pkg = mPackageName; final Intent intent = new Intent(PLUGIN_CHANGED, pkg != null ? Uri.fromParts("package", pkg, null) : null); getContext().sendBroadcast(intent); @@ -268,8 +279,7 @@ public class DeveloperOptionsFragment extends PreferenceFragment { : View.GONE); holder.findViewById(R.id.settings).setOnClickListener(v -> { ResolveInfo result = v.getContext().getPackageManager().resolveActivity( - new Intent(ACTION_PLUGIN_SETTINGS).setPackage( - mInfo.packageName), 0); + new Intent(ACTION_PLUGIN_SETTINGS).setPackage(mPackageName), 0); if (result != null) { v.getContext().startActivity(new Intent().setComponent( new ComponentName(result.activityInfo.packageName, @@ -278,7 +288,7 @@ public class DeveloperOptionsFragment extends PreferenceFragment { }); holder.itemView.setOnLongClickListener(v -> { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - intent.setData(Uri.fromParts("package", mInfo.packageName, null)); + intent.setData(Uri.fromParts("package", mPackageName, null)); getContext().startActivity(intent); return true; }); diff --git a/src/com/android/launcher3/util/DynamicResource.java b/src/com/android/launcher3/util/DynamicResource.java new file mode 100644 index 0000000000..8a75767f42 --- /dev/null +++ b/src/com/android/launcher3/util/DynamicResource.java @@ -0,0 +1,89 @@ +/* + * 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.util; + +import android.content.Context; + +import androidx.annotation.ColorRes; +import androidx.annotation.DimenRes; +import androidx.annotation.FractionRes; +import androidx.annotation.IntegerRes; + +import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.plugins.ResourceProvider; + +/** + * Utility class to support customizing resource values using plugins + * + * To load resources, call + * DynamicResource.provider(context).getInt(resId) or any other supported methods + * + * To allow customization for a particular resource, add them to dynamic_resources.xml + */ +public class DynamicResource implements ResourceProvider, PluginListener { + + private static final MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(DynamicResource::new); + + private final Context mContext; + private ResourceProvider mPlugin; + + private DynamicResource(Context context) { + mContext = context; + PluginManagerWrapper.INSTANCE.get(context).addPluginListener(this, + ResourceProvider.class, false /* allowedMultiple */); + } + + @Override + public int getInt(@IntegerRes int resId) { + return mContext.getResources().getInteger(resId); + } + + @Override + public float getFraction(@FractionRes int resId) { + return mContext.getResources().getFraction(resId, 1, 1); + } + + @Override + public float getDimension(@DimenRes int resId) { + return mContext.getResources().getDimension(resId); + } + + @Override + public int getColor(@ColorRes int resId) { + return mContext.getResources().getColor(resId, null); + } + + @Override + public void onPluginConnected(ResourceProvider plugin, Context context) { + mPlugin = plugin; + } + + @Override + public void onPluginDisconnected(ResourceProvider plugin) { + mPlugin = null; + } + + /** + * Returns the currently active or default provider + */ + public static ResourceProvider provider(Context context) { + DynamicResource dr = DynamicResource.INSTANCE.get(context); + ResourceProvider plugin = dr.mPlugin; + return plugin == null ? dr : plugin; + } +} diff --git a/src_plugins/com/android/systemui/plugins/ResourceProvider.java b/src_plugins/com/android/systemui/plugins/ResourceProvider.java new file mode 100644 index 0000000000..eaed9e7f1c --- /dev/null +++ b/src_plugins/com/android/systemui/plugins/ResourceProvider.java @@ -0,0 +1,47 @@ +/* + * 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.systemui.plugins; + +import com.android.systemui.plugins.annotations.ProvidesInterface; + +/** + * Plugin to support customizing resource + */ +@ProvidesInterface(action = ResourceProvider.ACTION, version = ResourceProvider.VERSION) +public interface ResourceProvider extends Plugin { + String ACTION = "com.android.launcher3.action.PLUGIN_DYNAMIC_RESOURCE"; + int VERSION = 1; + + /** + * @see android.content.res.Resources#getInteger(int) + */ + int getInt(int resId); + + /** + * @see android.content.res.Resources#getFraction(int, int, int) + */ + float getFraction(int resId); + + /** + * @see android.content.res.Resources#getDimension(int) + */ + float getDimension(int resId); + + /** + * @see android.content.res.Resources#getColor(int) + */ + int getColor(int resId); +} From 4df42f85a40b963802fc736939bdd4c17bc1e029 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 13 Nov 2019 15:38:28 -0800 Subject: [PATCH 02/24] Fix recents scale sometimes lagging behind window scale We were previously offsetting the launcher animation progress based on when we got onGestureStart, which meant it would lag behind if onGestureStart came late. Now that we track the window instead of the launcher shelf, and we don't show the launcher animation right away in fully gestural mode anyway, we should remove this to ensure the launcher part of the animation always lines up with the app window. We also reapply state whenever predictions are enabled, e.g. when launcher starts after being force stopped, and previously this was canceling the existing state animation. We don't want to do that because predictions can be enabled at any point on a cold start, and cancelling the existing state animation means that RecentsView shows up in fullscreen and not attached to the app window for the duration of the gesture. Bug: 144454486 Change-Id: I65a2c71c9acd2f5345941ea2cff7d32c04b7be3f --- .../android/launcher3/appprediction/PredictionRowView.java | 2 +- .../src/com/android/quickstep/LauncherSwipeHandler.java | 7 +------ src/com/android/launcher3/Launcher.java | 6 +++++- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java index 23db5df2ed..f82af62aac 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java @@ -276,7 +276,7 @@ public class PredictionRowView extends LinearLayout implements boolean predictionsEnabled = predictionCount > 0; if (predictionsEnabled != mPredictionsEnabled) { mPredictionsEnabled = predictionsEnabled; - mLauncher.reapplyUi(); + mLauncher.reapplyUi(false /* cancelCurrentAnimation */); updateVisibility(); } mParent.onHeightUpdated(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java index 3d664bec7e..fe3926a3c3 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java @@ -167,8 +167,6 @@ public class LauncherSwipeHandler private boolean mIsShelfPeeking; private boolean mContinuingLastGesture; - // To avoid UI jump when gesture is started, we offset the animation by the threshold. - private float mShiftAtGestureStart = 0; private ThumbnailData mTaskSnapshot; @@ -574,9 +572,7 @@ public class LauncherSwipeHandler // Normalize the progress to 0 to 1, as the animation controller will clamp it to that // anyway. The controller mimics the drag length factor by applying it to its interpolators. float progress = mCurrentShift.value / mDragLengthFactor; - mLauncherTransitionController.setPlayFraction( - progress <= mShiftAtGestureStart || mShiftAtGestureStart >= 1 - ? 0 : (progress - mShiftAtGestureStart) / (1 - mShiftAtGestureStart)); + mLauncherTransitionController.setPlayFraction(progress); } /** @@ -626,7 +622,6 @@ public class LauncherSwipeHandler @Override public void onGestureStarted() { notifyGestureStartedAsync(); - mShiftAtGestureStart = mCurrentShift.value; mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED); mGestureStarted = true; } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index aafabff45c..577acb27f6 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -507,12 +507,16 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @Override public void reapplyUi() { + reapplyUi(true /* cancelCurrentAnimation */); + } + + public void reapplyUi(boolean cancelCurrentAnimation) { if (supportsFakeLandscapeUI()) { mRotationMode = mStableDeviceProfile == null ? RotationMode.NORMAL : getFakeRotationMode(mDeviceProfile); } getRootView().dispatchInsets(); - getStateManager().reapplyState(true /* cancelCurrentAnimation */); + getStateManager().reapplyState(cancelCurrentAnimation); } @Override From 42a9ef0e9f87230930127f25e0ce80b5ea1f603a Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Tue, 24 Sep 2019 16:05:54 -0700 Subject: [PATCH 03/24] Move shelf peeking anim code to ShelfPeekAnim class Bug: 126596417 Change-Id: I5ba501b04b3eaf12a20ba1312ef362f9734761c3 Merged-In: I5ba501b04b3eaf12a20ba1312ef362f9734761c3 --- .../android/quickstep/util/ShelfPeekAnim.java | 27 +++++ .../LauncherActivityControllerHelper.java | 33 ++---- .../WindowTransformSwipeHandler.java | 10 +- .../android/quickstep/util/ShelfPeekAnim.java | 104 ++++++++++++++++++ .../QuickstepAppTransitionManagerImpl.java | 15 ++- .../quickstep/ActivityControlHelper.java | 15 +-- 6 files changed, 158 insertions(+), 46 deletions(-) create mode 100644 go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java create mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java diff --git a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java new file mode 100644 index 0000000000..1e9ac6fbfe --- /dev/null +++ b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java @@ -0,0 +1,27 @@ +/* + * 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.quickstep.util; + +import com.android.launcher3.Launcher; + +/** Empty class, only exists so that l3goWithQuickstepIconRecentsDebug compiles. */ +public class ShelfPeekAnim { + public ShelfPeekAnim(Launcher launcher) { + } + + public enum ShelfAnimState { + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java index 57e6d96ef2..c84b818994 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -54,14 +54,16 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherInitListenerEx; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager; +import com.android.launcher3.QuickstepAppTransitionManagerImpl; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; -import com.android.launcher3.uioverrides.states.OverviewState; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.util.LayoutUtils; +import com.android.quickstep.util.ShelfPeekAnim; +import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState; import com.android.quickstep.util.StaggeredWorkspaceAnim; import com.android.quickstep.views.LauncherRecentsView; import com.android.quickstep.views.RecentsView; @@ -201,7 +203,9 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe activity.getAppsView().reset(false /* animate */); return new AnimationFactory() { - private ShelfAnimState mShelfState; + private final ShelfPeekAnim mShelfAnim = + ((QuickstepAppTransitionManagerImpl) activity.getAppTransitionManager()) + .getShelfPeekAnim(); private boolean mIsAttachedToWindow; @Override @@ -230,30 +234,7 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe @Override public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) { - if (mShelfState == shelfState) { - return; - } - mShelfState = shelfState; - activity.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM); - if (mShelfState == ShelfAnimState.CANCEL) { - return; - } - float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(activity); - float shelfOverviewProgress = OVERVIEW.getVerticalProgress(activity); - // Peek based on default overview progress so we can see hotseat if we're showing - // that instead of predictions in overview. - float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(activity); - float shelfPeekingProgress = shelfHiddenProgress - - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f; - float toProgress = mShelfState == ShelfAnimState.HIDE - ? shelfHiddenProgress - : mShelfState == ShelfAnimState.PEEK - ? shelfPeekingProgress - : shelfOverviewProgress; - Animator shelfAnim = activity.getStateManager() - .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress); - shelfAnim.setInterpolator(interpolator); - shelfAnim.setDuration(duration).start(); + mShelfAnim.setShelfState(shelfState, interpolator, duration); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java index c80dede537..ea53364c2b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -26,14 +26,14 @@ import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; import static com.android.launcher3.util.RaceConditionTracker.ENTER; import static com.android.launcher3.util.RaceConditionTracker.EXIT; import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; -import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.HIDE; -import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.PEEK; import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG; import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.HOME; import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.LAST_TASK; import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.NEW_TASK; import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.RECENTS; +import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE; +import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK; import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; @@ -74,13 +74,14 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.util.RaceConditionTracker; import com.android.launcher3.util.TraceHelper; import com.android.quickstep.ActivityControlHelper.AnimationFactory; -import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState; import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.inputconsumers.InputConsumer; import com.android.quickstep.inputconsumers.OverviewInputConsumer; import com.android.quickstep.util.ClipAnimationHelper.TargetAlphaProvider; import com.android.quickstep.util.RectFSpringAnim; +import com.android.quickstep.util.ShelfPeekAnim; +import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState; import com.android.quickstep.util.SwipeAnimationTargetSet; import com.android.quickstep.views.LiveTileOverlay; import com.android.quickstep.views.RecentsView; @@ -192,7 +193,6 @@ public class WindowTransformSwipeHandler Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW)); private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured"; - private static final long SHELF_ANIM_DURATION = 240; public static final long RECENTS_ATTACH_DURATION = 300; /** @@ -442,7 +442,7 @@ public class WindowTransformSwipeHandler @Override public void onMotionPauseChanged(boolean isPaused) { - setShelfState(isPaused ? PEEK : HIDE, OVERSHOOT_1_2, SHELF_ANIM_DURATION); + setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION); } public void maybeUpdateRecentsAttachedState() { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java new file mode 100644 index 0000000000..41be6834f7 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java @@ -0,0 +1,104 @@ +/* + * 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.quickstep.util; + +import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF_ANIM; +import static com.android.launcher3.LauncherState.BACKGROUND_APP; +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.view.animation.Interpolator; + +import com.android.launcher3.Launcher; +import com.android.launcher3.uioverrides.states.OverviewState; + +/** + * Animates the shelf between states HIDE, PEEK, and OVERVIEW. + */ +public class ShelfPeekAnim { + + public static final Interpolator INTERPOLATOR = OVERSHOOT_1_2; + public static final long DURATION = 240; + + private final Launcher mLauncher; + + private ShelfAnimState mShelfState; + private boolean mIsPeeking; + + public ShelfPeekAnim(Launcher launcher) { + mLauncher = launcher; + } + + /** + * Animates to the given state, canceling the previous animation if it was still running. + */ + public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) { + if (mShelfState == shelfState) { + return; + } + mLauncher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM); + mShelfState = shelfState; + mIsPeeking = mShelfState == ShelfAnimState.PEEK || mShelfState == ShelfAnimState.HIDE; + if (mShelfState == ShelfAnimState.CANCEL) { + return; + } + float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(mLauncher); + float shelfOverviewProgress = OVERVIEW.getVerticalProgress(mLauncher); + // Peek based on default overview progress so we can see hotseat if we're showing + // that instead of predictions in overview. + float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(mLauncher); + float shelfPeekingProgress = shelfHiddenProgress + - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f; + float toProgress = mShelfState == ShelfAnimState.HIDE + ? shelfHiddenProgress + : mShelfState == ShelfAnimState.PEEK + ? shelfPeekingProgress + : shelfOverviewProgress; + Animator shelfAnim = mLauncher.getStateManager() + .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress); + shelfAnim.setInterpolator(interpolator); + shelfAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + mShelfState = ShelfAnimState.CANCEL; + } + + @Override + public void onAnimationEnd(Animator animator) { + mIsPeeking = mShelfState == ShelfAnimState.PEEK; + } + }); + shelfAnim.setDuration(duration).start(); + } + + /** @return Whether the shelf is currently peeking or animating to or from peeking. */ + public boolean isPeeking() { + return mIsPeeking; + } + + /** The various shelf states we can animate to. */ + public enum ShelfAnimState { + HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false); + + ShelfAnimState(boolean shouldPreformHaptic) { + this.shouldPreformHaptic = shouldPreformHaptic; + } + + public final boolean shouldPreformHaptic; + } +} diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java index 991408c649..a91410cd9b 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java @@ -57,6 +57,9 @@ import android.os.Looper; import android.util.Pair; import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.Interpolators; @@ -69,6 +72,7 @@ import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.util.MultiValueUpdateListener; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.RemoteAnimationTargetSet; +import com.android.quickstep.util.ShelfPeekAnim; import com.android.systemui.shared.system.ActivityCompat; import com.android.systemui.shared.system.ActivityOptionsCompat; import com.android.systemui.shared.system.QuickStepContract; @@ -80,9 +84,6 @@ import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; import com.android.systemui.shared.system.WindowManagerWrapper; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - /** * {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from * home and/or all-apps. @@ -150,6 +151,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans private RemoteAnimationProvider mRemoteAnimationProvider; + private final ShelfPeekAnim mShelfPeekAnim; + private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { @@ -177,6 +180,12 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans mLauncher.addOnDeviceProfileChangeListener(this); registerRemoteAnimations(); + + mShelfPeekAnim = new ShelfPeekAnim(mLauncher); + } + + public ShelfPeekAnim getShelfPeekAnim() { + return mShelfPeekAnim; } @Override diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index 5c9c7d4cab..110cc23eca 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -36,6 +36,7 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.RemoteAnimationTargetSet; +import com.android.quickstep.util.ShelfPeekAnim; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import java.util.function.BiPredicate; @@ -109,16 +110,6 @@ public interface ActivityControlHelper { interface AnimationFactory { - enum ShelfAnimState { - HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false); - - ShelfAnimState(boolean shouldPreformHaptic) { - this.shouldPreformHaptic = shouldPreformHaptic; - } - - public final boolean shouldPreformHaptic; - } - default void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) { } void createActivityController(long transitionLength); @@ -127,8 +118,8 @@ public interface ActivityControlHelper { default void onTransitionCancelled() { } - default void setShelfState(ShelfAnimState animState, Interpolator interpolator, - long duration) { } + default void setShelfState(ShelfPeekAnim.ShelfAnimState animState, + Interpolator interpolator, long duration) { } /** * @param attached Whether to show RecentsView alongside the app window. If false, recents From 2785a5996cebaae4df29c1f9e9efcaf2f7b0055f Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Mon, 12 Aug 2019 18:33:48 -0700 Subject: [PATCH 04/24] Improve quick switch from home by tracking both x and y motion - Add NoButtonQuickSwitchTouchController which uses BothAxesSwipeDetector to track horizontal and vertical motion. - Initially, we only detect swipe left to right to quick switch (like before), but then we allow swipe up to either go to overview (if you hold) or back home (if you don't hold). - xDisplacement transitions non-overview components out (e.g. shelf and workspace), and translates overview in. - yDisplacement translates overview up and scales it down Bug: 126596417 Change-Id: Id679ad84c08246e205c667a78ed5df00d7276258 Merged-In: Id679ad84c08246e205c667a78ed5df00d7276258 --- .../android/quickstep/util/ShelfPeekAnim.java | 4 + .../LauncherAppTransitionManagerImpl.java | 27 +- .../uioverrides/RecentsUiFactory.java | 6 +- .../uioverrides/states/OverviewState.java | 2 + .../FlingAndHoldTouchController.java | 21 +- .../NoButtonQuickSwitchTouchController.java | 462 ++++++++++++++++++ .../LauncherActivityControllerHelper.java | 17 +- .../android/quickstep/util/ShelfPeekAnim.java | 1 + .../util/StaggeredWorkspaceAnim.java | 30 +- .../quickstep/views/ShelfScrimView.java | 10 +- .../launcher3/LauncherStateManager.java | 6 +- .../launcher3/anim/AlphaUpdateListener.java | 2 +- .../anim/AnimatorPlaybackController.java | 21 +- .../android/launcher3/anim/Interpolators.java | 2 + .../AbstractStateChangeTouchController.java | 5 +- 15 files changed, 559 insertions(+), 57 deletions(-) create mode 100644 quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java diff --git a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java index 1e9ac6fbfe..fb8901388b 100644 --- a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java +++ b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java @@ -24,4 +24,8 @@ public class ShelfPeekAnim { public enum ShelfAnimState { } + + public boolean isPeeking() { + return false; + } } 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 cdff33bf9b..114fd8e10b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -21,8 +21,15 @@ import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.LauncherStateManager.ANIM_ALL; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; +import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator; @@ -40,6 +47,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.LauncherState.ScaleAndTranslation; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.SpringAnimationBuilder; import com.android.quickstep.util.ClipAnimationHelper; @@ -56,6 +64,9 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti public static final int INDEX_SHELF_ANIM = 0; public static final int INDEX_RECENTS_FADE_ANIM = 1; public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2; + public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 3; + + public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300; public LauncherAppTransitionManagerImpl(Context context) { super(context); @@ -144,7 +155,7 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti @Override public int getStateElementAnimationsCount() { - return 3; + return 4; } @Override @@ -190,6 +201,20 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti .setStiffness(250) .setValues(values) .build(mLauncher); + case INDEX_PAUSE_TO_OVERVIEW_ANIM: { + AnimatorSetBuilder builder = new AnimatorSetBuilder(); + builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2); + builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3); + if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { + builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2); + builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2); + } + LauncherStateManager stateManager = mLauncher.getStateManager(); + return stateManager.createAtomicAnimation( + stateManager.getCurrentStableState(), OVERVIEW, builder, + ANIM_ALL, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW); + } + default: return super.createStateElementAnimation(index, values); } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java index 596bc4f44c..cac170c68c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java @@ -28,17 +28,17 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager.StateHandler; -import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController; import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController; import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController; +import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController; import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController; import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController; -import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController; import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController; +import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController; import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController; import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController; import com.android.launcher3.util.TouchController; @@ -145,7 +145,7 @@ public abstract class RecentsUiFactory { ArrayList list = new ArrayList<>(); list.add(launcher.getDragController()); if (mode == NO_BUTTON) { - list.add(new QuickSwitchTouchController(launcher)); + list.add(new NoButtonQuickSwitchTouchController(launcher)); list.add(new NavBarToHomeTouchController(launcher)); list.add(new FlingAndHoldTouchController(launcher)); } else { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java index 25eaab1879..ed5dba1fd5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -22,6 +22,7 @@ import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; @@ -205,6 +206,7 @@ public class OverviewState extends LauncherState { builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2); builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2); builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7); + builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_7); builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java index 38a0b66730..3231f378db 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java @@ -16,25 +16,20 @@ package com.android.launcher3.uioverrides.touchcontrollers; +import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM; import static com.android.launcher3.LauncherState.ALL_APPS; -import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_PEEK; -import static com.android.launcher3.LauncherStateManager.ANIM_ALL; import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; -import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; @@ -46,6 +41,7 @@ import android.view.View; import android.view.ViewConfiguration; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppTransitionManagerImpl; import com.android.launcher3.LauncherState; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; @@ -79,7 +75,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { @Override protected long getAtomicDuration() { - return 300; + return LauncherAppTransitionManagerImpl.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW; } @Override @@ -179,15 +175,8 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { mPeekAnim.cancel(); } - AnimatorSetBuilder builder = new AnimatorSetBuilder(); - builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2); - builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3); - if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { - builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2); - builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2); - } - AnimatorSet overviewAnim = mLauncher.getStateManager().createAtomicAnimation( - NORMAL, OVERVIEW, builder, ANIM_ALL, ATOMIC_DURATION); + Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation( + INDEX_PAUSE_TO_OVERVIEW_ANIM); overviewAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java new file mode 100644 index 0000000000..7ffffeda10 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -0,0 +1,462 @@ +/* + * 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.uioverrides.touchcontrollers; + +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM; +import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; +import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.LauncherState.QUICK_SWITCH; +import static com.android.launcher3.LauncherStateManager.ANIM_ALL; +import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; +import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW; +import static com.android.launcher3.anim.Interpolators.ACCEL_0_75; +import static com.android.launcher3.anim.Interpolators.DEACCEL; +import static com.android.launcher3.anim.Interpolators.DEACCEL_5; +import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; +import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT; +import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP; +import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; +import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; +import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.CANCEL; +import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE; +import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK; +import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.graphics.PointF; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.Interpolator; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherState; +import com.android.launcher3.LauncherStateManager; +import com.android.launcher3.LauncherStateManager.AnimationConfig; +import com.android.launcher3.QuickstepAppTransitionManagerImpl; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.allapps.AllAppsTransitionController; +import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.AnimatorSetBuilder; +import com.android.launcher3.graphics.OverviewScrim; +import com.android.launcher3.touch.BaseSwipeDetector; +import com.android.launcher3.touch.BothAxesSwipeDetector; +import com.android.launcher3.userevent.nano.LauncherLogProto; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; +import com.android.launcher3.util.TouchController; +import com.android.launcher3.util.VibratorWrapper; +import com.android.quickstep.OverviewInteractionState; +import com.android.quickstep.util.LayoutUtils; +import com.android.quickstep.util.MotionPauseDetector; +import com.android.quickstep.util.ShelfPeekAnim; +import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState; +import com.android.quickstep.util.StaggeredWorkspaceAnim; +import com.android.quickstep.views.LauncherRecentsView; + +/** + * Handles quick switching to a recent task from the home screen. To give as much flexibility to + * the user as possible, also handles swipe up and hold to go to overview and swiping back home. + */ +public class NoButtonQuickSwitchTouchController implements TouchController, + BothAxesSwipeDetector.Listener, MotionPauseDetector.OnMotionPauseListener { + + /** The minimum progress of the scale/translationY animation until drag end. */ + private static final float Y_ANIM_MIN_PROGRESS = 0.15f; + private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_5; + private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75; + private static final Interpolator SCALE_DOWN_INTERPOLATOR = DEACCEL; + + private final Launcher mLauncher; + private final BothAxesSwipeDetector mSwipeDetector; + private final float mXRange; + private final float mYRange; + private final MotionPauseDetector mMotionPauseDetector; + private final float mMotionPauseMinDisplacement; + + private boolean mNoIntercept; + private LauncherState mStartState; + + private ShelfPeekAnim mShelfPeekAnim; + private boolean mIsHomeScreenVisible = true; + + // As we drag, we control 3 animations: one to get non-overview components out of the way, + // and the other two to set overview properties based on x and y progress. + private AnimatorPlaybackController mNonOverviewAnim; + private AnimatorPlaybackController mXOverviewAnim; + private AnimatorPlaybackController mYOverviewAnim; + + public NoButtonQuickSwitchTouchController(Launcher launcher) { + mLauncher = launcher; + mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this); + mXRange = mLauncher.getDeviceProfile().widthPx / 2f; + mYRange = LayoutUtils.getShelfTrackingDistance(mLauncher, mLauncher.getDeviceProfile()); + mMotionPauseDetector = new MotionPauseDetector(mLauncher); + mMotionPauseMinDisplacement = mLauncher.getResources().getDimension( + R.dimen.motion_pause_detector_min_displacement_from_app); + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mNoIntercept = !canInterceptTouch(ev); + if (mNoIntercept) { + return false; + } + + // Only detect horizontal swipe for intercept, then we will allow swipe up as well. + mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT, + false /* ignoreSlopWhenSettling */); + } + + if (mNoIntercept) { + return false; + } + + onControllerTouchEvent(ev); + return mSwipeDetector.isDraggingOrSettling(); + } + + @Override + public boolean onControllerTouchEvent(MotionEvent ev) { + return mSwipeDetector.onTouchEvent(ev); + } + + private boolean canInterceptTouch(MotionEvent ev) { + if (!mLauncher.isInState(LauncherState.NORMAL)) { + return false; + } + if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) { + return false; + } + int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags(); + if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) { + return false; + } + return true; + } + + @Override + public void onDragStart(boolean start) { + mMotionPauseDetector.clear(); + if (start) { + mShelfPeekAnim = ((QuickstepAppTransitionManagerImpl) mLauncher + .getAppTransitionManager()).getShelfPeekAnim(); + + mStartState = mLauncher.getStateManager().getState(); + + mMotionPauseDetector.setOnMotionPauseListener(this); + + // We have detected horizontal drag start, now allow swipe up as well. + mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT | DIRECTION_UP, + false /* ignoreSlopWhenSettling */); + + setupAnimators(); + } + } + + @Override + public void onMotionPauseChanged(boolean isPaused) { + ShelfAnimState shelfState = isPaused ? PEEK : HIDE; + if (shelfState == PEEK) { + // Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking. + AnimatorSetBuilder builder = new AnimatorSetBuilder(); + AllAppsTransitionController allAppsController = mLauncher.getAllAppsController(); + allAppsController.setAlphas(NORMAL.getVisibleElements(mLauncher), + new AnimationConfig(), builder); + builder.build().setDuration(0).start(); + + if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { + // Hotseat was hidden, but we need it visible when peeking. + mLauncher.getHotseat().setAlpha(1); + } + } + mShelfPeekAnim.setShelfState(shelfState, ShelfPeekAnim.INTERPOLATOR, + ShelfPeekAnim.DURATION); + VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC); + } + + private void setupAnimators() { + // Animate the non-overview components (e.g. workspace, shelf) out of the way. + AnimatorSetBuilder nonOverviewBuilder = new AnimatorSetBuilder(); + nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_FADE, FADE_OUT_INTERPOLATOR); + nonOverviewBuilder.setInterpolator(ANIM_ALL_APPS_FADE, FADE_OUT_INTERPOLATOR); + nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, TRANSLATE_OUT_INTERPOLATOR); + nonOverviewBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, TRANSLATE_OUT_INTERPOLATOR); + updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder, ANIM_ALL); + mNonOverviewAnim.dispatchOnStart(); + + setupOverviewAnimators(); + } + + /** Create state animation to control non-overview components. */ + private void updateNonOverviewAnim(LauncherState toState, AnimatorSetBuilder builder, + @LauncherStateManager.AnimationComponents int animComponents) { + builder.addFlag(FLAG_DONT_ANIMATE_OVERVIEW); + long accuracy = (long) (Math.max(mXRange, mYRange) * 2); + mNonOverviewAnim = mLauncher.getStateManager().createAnimationToNewWorkspace(toState, + builder, accuracy, this::clearState, animComponents); + } + + private void setupOverviewAnimators() { + final LauncherState fromState = QUICK_SWITCH; + final LauncherState toState = OVERVIEW; + LauncherState.ScaleAndTranslation fromScaleAndTranslation = fromState + .getOverviewScaleAndTranslation(mLauncher); + LauncherState.ScaleAndTranslation toScaleAndTranslation = toState + .getOverviewScaleAndTranslation(mLauncher); + // Update RecentView's translationX to have it start offscreen. + LauncherRecentsView recentsView = mLauncher.getOverviewPanel(); + float startScale = Utilities.mapRange( + SCALE_DOWN_INTERPOLATOR.getInterpolation(Y_ANIM_MIN_PROGRESS), + fromScaleAndTranslation.scale, + toScaleAndTranslation.scale); + fromScaleAndTranslation.translationX = recentsView.getOffscreenTranslationX(startScale); + + // Set RecentView's initial properties. + recentsView.setScaleX(fromScaleAndTranslation.scale); + recentsView.setScaleY(fromScaleAndTranslation.scale); + recentsView.setTranslationX(fromScaleAndTranslation.translationX); + recentsView.setTranslationY(fromScaleAndTranslation.translationY); + recentsView.setContentAlpha(1); + + // As we drag right, animate the following properties: + // - RecentsView translationX + // - OverviewScrim + AnimatorSet xOverviewAnim = new AnimatorSet(); + xOverviewAnim.play(ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_X, + toScaleAndTranslation.translationX)); + xOverviewAnim.play(ObjectAnimator.ofFloat( + mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS, + toState.getOverviewScrimAlpha(mLauncher))); + long xAccuracy = (long) (mXRange * 2); + xOverviewAnim.setDuration(xAccuracy); + mXOverviewAnim = AnimatorPlaybackController.wrap(xOverviewAnim, xAccuracy); + mXOverviewAnim.dispatchOnStart(); + + // As we drag up, animate the following properties: + // - RecentsView translationY + // - RecentsView scale + // - RecentsView fullscreenProgress + AnimatorSet yAnimation = new AnimatorSet(); + Animator translateYAnim = ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_Y, + toScaleAndTranslation.translationY); + Animator scaleAnim = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, + toScaleAndTranslation.scale); + Animator fullscreenProgressAnim = ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, + fromState.getOverviewFullscreenProgress(), toState.getOverviewFullscreenProgress()); + scaleAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR); + fullscreenProgressAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR); + yAnimation.play(translateYAnim); + yAnimation.play(scaleAnim); + yAnimation.play(fullscreenProgressAnim); + long yAccuracy = (long) (mYRange * 2); + yAnimation.setDuration(yAccuracy); + mYOverviewAnim = AnimatorPlaybackController.wrap(yAnimation, yAccuracy); + mYOverviewAnim.dispatchOnStart(); + } + + @Override + public boolean onDrag(PointF displacement, MotionEvent ev) { + float xProgress = Math.max(0, displacement.x) / mXRange; + float yProgress = Math.max(0, -displacement.y) / mYRange; + yProgress = Utilities.mapRange(yProgress, Y_ANIM_MIN_PROGRESS, 1f); + + boolean wasHomeScreenVisible = mIsHomeScreenVisible; + if (wasHomeScreenVisible && mNonOverviewAnim != null) { + mNonOverviewAnim.setPlayFraction(xProgress); + } + mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress) + <= 1 - ALPHA_CUTOFF_THRESHOLD; + + if (wasHomeScreenVisible && !mIsHomeScreenVisible) { + // Get the shelf all the way offscreen so it pops up when we decide to peek it. + mShelfPeekAnim.setShelfState(HIDE, LINEAR, 0); + } + + // Only allow motion pause if the home screen is invisible, since some + // home screen elements will appear in the shelf on motion pause. + mMotionPauseDetector.setDisallowPause(mIsHomeScreenVisible + || -displacement.y < mMotionPauseMinDisplacement); + mMotionPauseDetector.addPosition(displacement.y, ev.getEventTime()); + + if (mIsHomeScreenVisible) { + // Cancel the shelf anim so it doesn't clobber mNonOverviewAnim. + mShelfPeekAnim.setShelfState(CANCEL, LINEAR, 0); + } + + if (mXOverviewAnim != null) { + mXOverviewAnim.setPlayFraction(xProgress); + } + if (mYOverviewAnim != null) { + mYOverviewAnim.setPlayFraction(yProgress); + } + return true; + } + + @Override + public void onDragEnd(PointF velocity) { + boolean horizontalFling = mSwipeDetector.isFling(velocity.x); + boolean verticalFling = mSwipeDetector.isFling(velocity.y); + boolean noFling = !horizontalFling && !verticalFling; + int logAction = noFling ? Touch.SWIPE : Touch.FLING; + if (mMotionPauseDetector.isPaused() && noFling) { + cancelAnimations(); + + Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation( + INDEX_PAUSE_TO_OVERVIEW_ANIM); + overviewAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + onAnimationToStateCompleted(OVERVIEW, logAction); + } + }); + overviewAnim.start(); + return; + } + + final LauncherState targetState; + if (horizontalFling && verticalFling) { + // Flinging left and up, left and down, or right and up all go back home. + // Only flinging right and down goes to quick switch. + targetState = velocity.x < 0 || velocity.y < 0 ? NORMAL : QUICK_SWITCH; + } else if (horizontalFling) { + targetState = velocity.x > 0 ? QUICK_SWITCH : NORMAL; + } else if (verticalFling) { + targetState = velocity.y > 0 ? QUICK_SWITCH : NORMAL; + } else { + // If user isn't flinging, just snap to the closest state based on x progress. + boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f; + targetState = passedHorizontalThreshold ? QUICK_SWITCH : NORMAL; + } + + // Animate the various components to the target state. + + float xProgress = mXOverviewAnim.getProgressFraction(); + float startXProgress = Utilities.boundToRange(xProgress + + velocity.x * getSingleFrameMs(mLauncher) / mXRange, 0f, 1f); + final float endXProgress = targetState == NORMAL ? 0 : 1; + long xDuration = BaseSwipeDetector.calculateDuration(velocity.x, + Math.abs(endXProgress - startXProgress)); + ValueAnimator xOverviewAnim = mXOverviewAnim.getAnimationPlayer(); + xOverviewAnim.setFloatValues(startXProgress, endXProgress); + xOverviewAnim.setDuration(xDuration) + .setInterpolator(scrollInterpolatorForVelocity(velocity.x)); + mXOverviewAnim.dispatchOnStartWithVelocity(endXProgress, velocity.x); + + boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL; + + float yProgress = mYOverviewAnim.getProgressFraction(); + float startYProgress = Utilities.boundToRange(yProgress + - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, 1f); + final float endYProgress; + if (flingUpToNormal) { + endYProgress = 1; + } else if (targetState == NORMAL) { + // Keep overview at its current scale/translationY as it slides off the screen. + endYProgress = startYProgress; + } else { + endYProgress = 0; + } + long yDuration = BaseSwipeDetector.calculateDuration(velocity.y, + Math.abs(endYProgress - startYProgress)); + ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer(); + yOverviewAnim.setFloatValues(startYProgress, endYProgress); + yOverviewAnim.setDuration(yDuration); + mYOverviewAnim.dispatchOnStartWithVelocity(endYProgress, velocity.y); + + ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer(); + if (flingUpToNormal && !mIsHomeScreenVisible) { + // We are flinging to home while workspace is invisible, run the same staggered + // animation as from an app. + // Update mNonOverviewAnim to do nothing so it doesn't interfere. + updateNonOverviewAnim(targetState, new AnimatorSetBuilder(), 0 /* animComponents */); + nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer(); + + new StaggeredWorkspaceAnim(mLauncher, null, velocity.y, + false /* animateOverviewScrim */).start(); + } else { + boolean canceled = targetState == NORMAL; + if (canceled) { + // Let the state manager know that the animation didn't go to the target state, + // but don't clean up yet (we already clean up when the animation completes). + mNonOverviewAnim.dispatchOnCancelWithoutCancelRunnable(); + } + float startProgress = mNonOverviewAnim.getProgressFraction(); + float endProgress = canceled ? 0 : 1; + nonOverviewAnim.setFloatValues(startProgress, endProgress); + mNonOverviewAnim.dispatchOnStartWithVelocity(endProgress, + horizontalFling ? velocity.x : velocity.y); + } + + nonOverviewAnim.setDuration(Math.max(xDuration, yDuration)); + mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState, logAction)); + + cancelAnimations(); + xOverviewAnim.start(); + yOverviewAnim.start(); + nonOverviewAnim.start(); + } + + private void onAnimationToStateCompleted(LauncherState targetState, int logAction) { + mLauncher.getUserEventDispatcher().logStateChangeAction(logAction, + getDirectionForLog(), mSwipeDetector.getDownX(), mSwipeDetector.getDownY(), + LauncherLogProto.ContainerType.NAVBAR, + mStartState.containerType, + targetState.containerType, + mLauncher.getWorkspace().getCurrentPage()); + mLauncher.getStateManager().goToState(targetState, false, this::clearState); + } + + private int getDirectionForLog() { + return Utilities.isRtl(mLauncher.getResources()) ? Direction.LEFT : Direction.RIGHT; + } + + private void cancelAnimations() { + if (mNonOverviewAnim != null) { + mNonOverviewAnim.getAnimationPlayer().cancel(); + } + if (mXOverviewAnim != null) { + mXOverviewAnim.getAnimationPlayer().cancel(); + } + if (mYOverviewAnim != null) { + mYOverviewAnim.getAnimationPlayer().cancel(); + } + mShelfPeekAnim.setShelfState(ShelfAnimState.CANCEL, LINEAR, 0); + mMotionPauseDetector.clear(); + } + + private void clearState() { + cancelAnimations(); + mNonOverviewAnim = null; + mXOverviewAnim = null; + mYOverviewAnim = null; + mIsHomeScreenVisible = true; + mSwipeDetector.finishedScrolling(); + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java index c84b818994..eefde12860 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -24,7 +24,6 @@ import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.launcher3.LauncherStateManager.ANIM_ALL; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.launcher3.anim.Interpolators.INSTANT; @@ -53,11 +52,9 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherInitListenerEx; import com.android.launcher3.LauncherState; -import com.android.launcher3.LauncherStateManager; import com.android.launcher3.QuickstepAppTransitionManagerImpl; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.SysUINavigationMode.Mode; @@ -169,18 +166,8 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe @Override public void playAtomicAnimation(float velocity) { - // Setup workspace with 0 duration to prepare for our staggered animation. - LauncherStateManager stateManager = activity.getStateManager(); - AnimatorSetBuilder builder = new AnimatorSetBuilder(); - // setRecentsAttachedToAppWindow() will animate recents out. - builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW); - stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0); - builder.build().start(); - - // Stop scrolling so that it doesn't interfere with the translation offscreen. - recentsView.getScroller().forceFinished(true); - - new StaggeredWorkspaceAnim(activity, workspaceView, velocity).start(); + new StaggeredWorkspaceAnim(activity, workspaceView, velocity, + true /* animateOverviewScrim */).start(); } }; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java index 41be6834f7..83bc416239 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java @@ -30,6 +30,7 @@ import com.android.launcher3.uioverrides.states.OverviewState; /** * Animates the shelf between states HIDE, PEEK, and OVERVIEW. */ + public class ShelfPeekAnim { public static final Interpolator INTERPOLATOR = OVERSHOOT_1_2; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index 1aa5365fd2..5f02de2b9c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -18,6 +18,7 @@ package com.android.quickstep.util; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.LauncherStateManager.ANIM_ALL; import static com.android.launcher3.anim.Interpolators.LINEAR; import android.animation.Animator; @@ -29,11 +30,11 @@ import android.view.ViewGroup; import androidx.annotation.Nullable; -import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; +import com.android.launcher3.LauncherStateManager; import com.android.launcher3.LauncherStateManager.AnimationConfig; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; @@ -41,9 +42,9 @@ import com.android.launcher3.Workspace; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.anim.SpringObjectAnimator; -import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.graphics.OverviewScrim; import com.android.launcher3.views.IconLabelDotView; +import com.android.quickstep.views.RecentsView; import java.util.ArrayList; import java.util.List; @@ -75,7 +76,9 @@ public class StaggeredWorkspaceAnim { * @param floatingViewOriginalView The FloatingIconView's original view. */ public StaggeredWorkspaceAnim(Launcher launcher, @Nullable View floatingViewOriginalView, - float velocity) { + float velocity, boolean animateOverviewScrim) { + prepareToAnimate(launcher); + mVelocity = velocity; mOriginalView = floatingViewOriginalView; @@ -133,8 +136,10 @@ public class StaggeredWorkspaceAnim { addStaggeredAnimationForView(qsb, grid.inv.numRows + 2, totalRows); } - addScrimAnimationForState(launcher, BACKGROUND_APP, 0); - addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS); + if (animateOverviewScrim) { + addScrimAnimationForState(launcher, BACKGROUND_APP, 0); + addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS); + } AnimatorListener resetClipListener = new AnimatorListenerAdapter() { int numAnimations = mAnimators.size(); @@ -160,6 +165,21 @@ public class StaggeredWorkspaceAnim { } } + /** + * Setup workspace with 0 duration to prepare for our staggered animation. + */ + private void prepareToAnimate(Launcher launcher) { + LauncherStateManager stateManager = launcher.getStateManager(); + AnimatorSetBuilder builder = new AnimatorSetBuilder(); + // setRecentsAttachedToAppWindow() will animate recents out. + builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW); + stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0); + builder.build().start(); + + // Stop scrolling so that it doesn't interfere with the translation offscreen. + launcher.getOverviewPanel().getScroller().forceFinished(true); + } + /** * Starts the animation. */ diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java index 0e591cac7a..3320dae73f 100644 --- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java +++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java @@ -18,6 +18,7 @@ package com.android.quickstep.views; import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.LauncherState.QUICK_SWITCH; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.LINEAR; @@ -35,6 +36,8 @@ import android.util.AttributeSet; import android.view.animation.Interpolator; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherState; +import com.android.launcher3.QuickstepAppTransitionManagerImpl; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.Interpolators; @@ -44,6 +47,7 @@ import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.util.LayoutUtils; +import com.android.quickstep.util.ShelfPeekAnim; /** * Scrim used for all-apps and shelf in Overview @@ -193,8 +197,12 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis if (mProgress >= 1) { mRemainingScreenColor = 0; mShelfColor = 0; + ShelfPeekAnim shelfPeekAnim = ((QuickstepAppTransitionManagerImpl) + mLauncher.getAppTransitionManager()).getShelfPeekAnim(); + LauncherState state = mLauncher.getStateManager().getState(); if (mSysUINavigationMode == Mode.NO_BUTTON - && mLauncher.getStateManager().getState() == BACKGROUND_APP) { + && (state == BACKGROUND_APP || state == QUICK_SWITCH) + && shelfPeekAnim.isPeeking()) { // Show the shelf background when peeking during swipe up. mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha); } diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java index 848e19fb53..f673508844 100644 --- a/src/com/android/launcher3/LauncherStateManager.java +++ b/src/com/android/launcher3/LauncherStateManager.java @@ -24,7 +24,8 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.os.Handler; import android.os.Looper; -import android.util.Log; + +import androidx.annotation.IntDef; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; @@ -32,7 +33,6 @@ import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter; import com.android.launcher3.compat.AccessibilityManagerCompat; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.uioverrides.UiFactory; import java.io.PrintWriter; @@ -40,8 +40,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import androidx.annotation.IntDef; - /** * TODO: figure out what kind of tests we can write for this * diff --git a/src/com/android/launcher3/anim/AlphaUpdateListener.java b/src/com/android/launcher3/anim/AlphaUpdateListener.java index 8ac9d662cd..eabd283699 100644 --- a/src/com/android/launcher3/anim/AlphaUpdateListener.java +++ b/src/com/android/launcher3/anim/AlphaUpdateListener.java @@ -27,7 +27,7 @@ import android.view.ViewGroup; */ public class AlphaUpdateListener extends AnimationSuccessListener implements AnimatorUpdateListener { - private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f; + public static final float ALPHA_CUTOFF_THRESHOLD = 0.01f; private View mView; diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java index 2c440bba11..4a52795f89 100644 --- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java +++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java @@ -26,15 +26,15 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.util.Log; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; + import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; -import androidx.dynamicanimation.animation.DynamicAnimation; -import androidx.dynamicanimation.animation.SpringAnimation; - /** * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators * and durations. @@ -250,6 +250,17 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat } } + /** + * Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This + * is intended to be used only if you need to cancel but want to defer cleaning up yourself. + */ + public void dispatchOnCancelWithoutCancelRunnable() { + Runnable onCancel = mOnCancelRunnable; + setOnCancelRunnable(null); + dispatchOnCancel(); + setOnCancelRunnable(onCancel); + } + public void dispatchOnCancel() { dispatchOnCancelRecursively(mAnim); } @@ -283,10 +294,6 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat mOnCancelRunnable = runnable; } - public Runnable getOnCancelRunnable() { - return mOnCancelRunnable; - } - public void skipToEnd() { mSkipToEnd = true; for (SpringAnimation spring : mSprings) { diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java index c45cd85aad..fccc120900 100644 --- a/src/com/android/launcher3/anim/Interpolators.java +++ b/src/com/android/launcher3/anim/Interpolators.java @@ -39,6 +39,7 @@ public class Interpolators { public static final Interpolator LINEAR = new LinearInterpolator(); public static final Interpolator ACCEL = new AccelerateInterpolator(); + public static final Interpolator ACCEL_0_75 = new AccelerateInterpolator(0.75f); public static final Interpolator ACCEL_1_5 = new AccelerateInterpolator(1.5f); public static final Interpolator ACCEL_2 = new AccelerateInterpolator(2); @@ -48,6 +49,7 @@ public class Interpolators { public static final Interpolator DEACCEL_2 = new DecelerateInterpolator(2); public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f); public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f); + public static final Interpolator DEACCEL_5 = new DecelerateInterpolator(5f); public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator(); diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 60f6ee9c52..f40f9762d0 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -413,10 +413,7 @@ public abstract class AbstractStateChangeTouchController } else { // Let the state manager know that the animation didn't go to the target state, // but don't cancel ourselves (we already clean up when the animation completes). - Runnable onCancel = mCurrentAnimation.getOnCancelRunnable(); - mCurrentAnimation.setOnCancelRunnable(null); - mCurrentAnimation.dispatchOnCancel(); - mCurrentAnimation.setOnCancelRunnable(onCancel); + mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(); endProgress = 0; if (progress <= 0) { From 37a0970bf536e3708b7fd562a09370067ec995eb Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Mon, 12 Aug 2019 18:33:48 -0700 Subject: [PATCH 05/24] Improve quick switch from home by tracking both x and y motion - Add NoButtonQuickSwitchTouchController which uses BothAxesSwipeDetector to track horizontal and vertical motion. - Initially, we only detect swipe left to right to quick switch (like before), but then we allow swipe up to either go to overview (if you hold) or back home (if you don't hold). - xDisplacement transitions non-overview components out (e.g. shelf and workspace), and translates overview in. - yDisplacement translates overview up and scales it down Bug: 126596417 Change-Id: Id679ad84c08246e205c667a78ed5df00d7276258 --- .../android/quickstep/util/ShelfPeekAnim.java | 4 + .../LauncherAppTransitionManagerImpl.java | 27 +- .../uioverrides/QuickstepLauncher.java | 3 +- .../uioverrides/states/OverviewState.java | 2 + .../FlingAndHoldTouchController.java | 23 +- .../NoButtonQuickSwitchTouchController.java | 459 ++++++++++++++++++ .../quickstep/LauncherActivityInterface.java | 17 +- .../util/StaggeredWorkspaceAnim.java | 28 +- .../quickstep/views/ShelfScrimView.java | 10 +- .../launcher3/anim/AlphaUpdateListener.java | 2 +- .../anim/AnimatorPlaybackController.java | 21 +- .../android/launcher3/anim/Interpolators.java | 2 + .../AbstractStateChangeTouchController.java | 5 +- .../android/launcher3/views/ScrimView.java | 6 +- 14 files changed, 555 insertions(+), 54 deletions(-) create mode 100644 quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java diff --git a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java index e7099ec1c9..3842efbe8f 100644 --- a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java +++ b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java @@ -24,4 +24,8 @@ public class ShelfPeekAnim { public enum ShelfAnimState { } + + public boolean isPeeking() { + return false; + } } 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 6946508fc7..51ee216139 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -21,8 +21,15 @@ import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.LauncherStateManager.ANIM_ALL; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; +import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator; @@ -40,6 +47,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.LauncherState.ScaleAndTranslation; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.SpringAnimationBuilder; import com.android.quickstep.util.AppWindowAnimationHelper; @@ -56,6 +64,9 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti public static final int INDEX_SHELF_ANIM = 0; public static final int INDEX_RECENTS_FADE_ANIM = 1; public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2; + public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 3; + + public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300; public LauncherAppTransitionManagerImpl(Context context) { super(context); @@ -145,7 +156,7 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti @Override public int getStateElementAnimationsCount() { - return 3; + return 4; } @Override @@ -191,6 +202,20 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti .setStiffness(250) .setValues(values) .build(mLauncher); + case INDEX_PAUSE_TO_OVERVIEW_ANIM: { + AnimatorSetBuilder builder = new AnimatorSetBuilder(); + builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2); + builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3); + if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { + builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2); + builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2); + } + LauncherStateManager stateManager = mLauncher.getStateManager(); + return stateManager.createAtomicAnimation( + stateManager.getCurrentStableState(), OVERVIEW, builder, + ANIM_ALL, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW); + } + default: return super.createStateElementAnimation(index, values); } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index eefb7dc039..1e5f9ffa15 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -37,6 +37,7 @@ import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController; import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController; import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController; +import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController; import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController; import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController; import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController; @@ -214,7 +215,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { ArrayList list = new ArrayList<>(); list.add(getDragController()); if (mode == NO_BUTTON) { - list.add(new QuickSwitchTouchController(this)); + list.add(new NoButtonQuickSwitchTouchController(this)); list.add(new NavBarToHomeTouchController(this)); list.add(new FlingAndHoldTouchController(this)); } else { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java index 25eaab1879..ed5dba1fd5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -22,6 +22,7 @@ import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; @@ -205,6 +206,7 @@ public class OverviewState extends LauncherState { builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2); builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2); builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7); + builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_7); builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java index 626292e867..d388f495bf 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java @@ -16,25 +16,20 @@ package com.android.launcher3.uioverrides.touchcontrollers; +import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM; import static com.android.launcher3.LauncherState.ALL_APPS; -import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_PEEK; -import static com.android.launcher3.LauncherStateManager.ANIM_ALL; import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; -import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; @@ -46,12 +41,13 @@ import android.view.View; import android.view.ViewConfiguration; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppTransitionManagerImpl; import com.android.launcher3.LauncherState; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; -import com.android.quickstep.SystemUiProxy; import com.android.launcher3.util.VibratorWrapper; +import com.android.quickstep.SystemUiProxy; import com.android.quickstep.util.MotionPauseDetector; import com.android.quickstep.views.RecentsView; @@ -79,7 +75,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { @Override protected long getAtomicDuration() { - return 300; + return LauncherAppTransitionManagerImpl.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW; } @Override @@ -179,15 +175,8 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { mPeekAnim.cancel(); } - AnimatorSetBuilder builder = new AnimatorSetBuilder(); - builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2); - builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3); - if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { - builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2); - builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2); - } - AnimatorSet overviewAnim = mLauncher.getStateManager().createAtomicAnimation( - NORMAL, OVERVIEW, builder, ANIM_ALL, ATOMIC_DURATION); + Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation( + INDEX_PAUSE_TO_OVERVIEW_ANIM); overviewAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java new file mode 100644 index 0000000000..54d165f5bd --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -0,0 +1,459 @@ +/* + * 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.uioverrides.touchcontrollers; + +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM; +import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; +import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.LauncherState.QUICK_SWITCH; +import static com.android.launcher3.LauncherStateManager.ANIM_ALL; +import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; +import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW; +import static com.android.launcher3.anim.Interpolators.ACCEL_0_75; +import static com.android.launcher3.anim.Interpolators.DEACCEL; +import static com.android.launcher3.anim.Interpolators.DEACCEL_5; +import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; +import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT; +import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP; +import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; +import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; +import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.CANCEL; +import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE; +import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK; +import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.graphics.PointF; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.Interpolator; + +import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.LauncherState; +import com.android.launcher3.LauncherStateManager; +import com.android.launcher3.LauncherStateManager.AnimationConfig; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.allapps.AllAppsTransitionController; +import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.AnimatorSetBuilder; +import com.android.launcher3.graphics.OverviewScrim; +import com.android.launcher3.touch.BaseSwipeDetector; +import com.android.launcher3.touch.BothAxesSwipeDetector; +import com.android.launcher3.userevent.nano.LauncherLogProto; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; +import com.android.launcher3.util.TouchController; +import com.android.launcher3.util.VibratorWrapper; +import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.util.LayoutUtils; +import com.android.quickstep.util.MotionPauseDetector; +import com.android.quickstep.util.ShelfPeekAnim; +import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState; +import com.android.quickstep.util.StaggeredWorkspaceAnim; +import com.android.quickstep.views.LauncherRecentsView; + +/** + * Handles quick switching to a recent task from the home screen. To give as much flexibility to + * the user as possible, also handles swipe up and hold to go to overview and swiping back home. + */ +public class NoButtonQuickSwitchTouchController implements TouchController, + BothAxesSwipeDetector.Listener, MotionPauseDetector.OnMotionPauseListener { + + /** The minimum progress of the scale/translationY animation until drag end. */ + private static final float Y_ANIM_MIN_PROGRESS = 0.15f; + private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_5; + private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75; + private static final Interpolator SCALE_DOWN_INTERPOLATOR = DEACCEL; + + private final BaseQuickstepLauncher mLauncher; + private final BothAxesSwipeDetector mSwipeDetector; + private final ShelfPeekAnim mShelfPeekAnim; + private final float mXRange; + private final float mYRange; + private final MotionPauseDetector mMotionPauseDetector; + private final float mMotionPauseMinDisplacement; + + private boolean mNoIntercept; + private LauncherState mStartState; + + private boolean mIsHomeScreenVisible = true; + + // As we drag, we control 3 animations: one to get non-overview components out of the way, + // and the other two to set overview properties based on x and y progress. + private AnimatorPlaybackController mNonOverviewAnim; + private AnimatorPlaybackController mXOverviewAnim; + private AnimatorPlaybackController mYOverviewAnim; + + public NoButtonQuickSwitchTouchController(BaseQuickstepLauncher launcher) { + mLauncher = launcher; + mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this); + mShelfPeekAnim = mLauncher.getShelfPeekAnim(); + mXRange = mLauncher.getDeviceProfile().widthPx / 2f; + mYRange = LayoutUtils.getShelfTrackingDistance(mLauncher, mLauncher.getDeviceProfile()); + mMotionPauseDetector = new MotionPauseDetector(mLauncher); + mMotionPauseMinDisplacement = mLauncher.getResources().getDimension( + R.dimen.motion_pause_detector_min_displacement_from_app); + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mNoIntercept = !canInterceptTouch(ev); + if (mNoIntercept) { + return false; + } + + // Only detect horizontal swipe for intercept, then we will allow swipe up as well. + mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT, + false /* ignoreSlopWhenSettling */); + } + + if (mNoIntercept) { + return false; + } + + onControllerTouchEvent(ev); + return mSwipeDetector.isDraggingOrSettling(); + } + + @Override + public boolean onControllerTouchEvent(MotionEvent ev) { + return mSwipeDetector.onTouchEvent(ev); + } + + private boolean canInterceptTouch(MotionEvent ev) { + if (!mLauncher.isInState(LauncherState.NORMAL)) { + return false; + } + if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) { + return false; + } + int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags(); + if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) { + return false; + } + return true; + } + + @Override + public void onDragStart(boolean start) { + mMotionPauseDetector.clear(); + if (start) { + mStartState = mLauncher.getStateManager().getState(); + + mMotionPauseDetector.setOnMotionPauseListener(this); + + // We have detected horizontal drag start, now allow swipe up as well. + mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT | DIRECTION_UP, + false /* ignoreSlopWhenSettling */); + + setupAnimators(); + } + } + + @Override + public void onMotionPauseChanged(boolean isPaused) { + ShelfAnimState shelfState = isPaused ? PEEK : HIDE; + if (shelfState == PEEK) { + // Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking. + AnimatorSetBuilder builder = new AnimatorSetBuilder(); + AllAppsTransitionController allAppsController = mLauncher.getAllAppsController(); + allAppsController.setAlphas(NORMAL.getVisibleElements(mLauncher), + new AnimationConfig(), builder); + builder.build().setDuration(0).start(); + + if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { + // Hotseat was hidden, but we need it visible when peeking. + mLauncher.getHotseat().setAlpha(1); + } + } + mShelfPeekAnim.setShelfState(shelfState, ShelfPeekAnim.INTERPOLATOR, + ShelfPeekAnim.DURATION); + VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC); + } + + private void setupAnimators() { + // Animate the non-overview components (e.g. workspace, shelf) out of the way. + AnimatorSetBuilder nonOverviewBuilder = new AnimatorSetBuilder(); + nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_FADE, FADE_OUT_INTERPOLATOR); + nonOverviewBuilder.setInterpolator(ANIM_ALL_APPS_FADE, FADE_OUT_INTERPOLATOR); + nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, TRANSLATE_OUT_INTERPOLATOR); + nonOverviewBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, TRANSLATE_OUT_INTERPOLATOR); + updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder, ANIM_ALL); + mNonOverviewAnim.dispatchOnStart(); + + setupOverviewAnimators(); + } + + /** Create state animation to control non-overview components. */ + private void updateNonOverviewAnim(LauncherState toState, AnimatorSetBuilder builder, + @LauncherStateManager.AnimationComponents int animComponents) { + builder.addFlag(FLAG_DONT_ANIMATE_OVERVIEW); + long accuracy = (long) (Math.max(mXRange, mYRange) * 2); + mNonOverviewAnim = mLauncher.getStateManager().createAnimationToNewWorkspace(toState, + builder, accuracy, this::clearState, animComponents); + } + + private void setupOverviewAnimators() { + final LauncherState fromState = QUICK_SWITCH; + final LauncherState toState = OVERVIEW; + LauncherState.ScaleAndTranslation fromScaleAndTranslation = fromState + .getOverviewScaleAndTranslation(mLauncher); + LauncherState.ScaleAndTranslation toScaleAndTranslation = toState + .getOverviewScaleAndTranslation(mLauncher); + // Update RecentView's translationX to have it start offscreen. + LauncherRecentsView recentsView = mLauncher.getOverviewPanel(); + float startScale = Utilities.mapRange( + SCALE_DOWN_INTERPOLATOR.getInterpolation(Y_ANIM_MIN_PROGRESS), + fromScaleAndTranslation.scale, + toScaleAndTranslation.scale); + fromScaleAndTranslation.translationX = recentsView.getOffscreenTranslationX(startScale); + + // Set RecentView's initial properties. + recentsView.setScaleX(fromScaleAndTranslation.scale); + recentsView.setScaleY(fromScaleAndTranslation.scale); + recentsView.setTranslationX(fromScaleAndTranslation.translationX); + recentsView.setTranslationY(fromScaleAndTranslation.translationY); + recentsView.setContentAlpha(1); + + // As we drag right, animate the following properties: + // - RecentsView translationX + // - OverviewScrim + AnimatorSet xOverviewAnim = new AnimatorSet(); + xOverviewAnim.play(ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_X, + toScaleAndTranslation.translationX)); + xOverviewAnim.play(ObjectAnimator.ofFloat( + mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS, + toState.getOverviewScrimAlpha(mLauncher))); + long xAccuracy = (long) (mXRange * 2); + xOverviewAnim.setDuration(xAccuracy); + mXOverviewAnim = AnimatorPlaybackController.wrap(xOverviewAnim, xAccuracy); + mXOverviewAnim.dispatchOnStart(); + + // As we drag up, animate the following properties: + // - RecentsView translationY + // - RecentsView scale + // - RecentsView fullscreenProgress + AnimatorSet yAnimation = new AnimatorSet(); + Animator translateYAnim = ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_Y, + toScaleAndTranslation.translationY); + Animator scaleAnim = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, + toScaleAndTranslation.scale); + Animator fullscreenProgressAnim = ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, + fromState.getOverviewFullscreenProgress(), toState.getOverviewFullscreenProgress()); + scaleAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR); + fullscreenProgressAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR); + yAnimation.play(translateYAnim); + yAnimation.play(scaleAnim); + yAnimation.play(fullscreenProgressAnim); + long yAccuracy = (long) (mYRange * 2); + yAnimation.setDuration(yAccuracy); + mYOverviewAnim = AnimatorPlaybackController.wrap(yAnimation, yAccuracy); + mYOverviewAnim.dispatchOnStart(); + } + + @Override + public boolean onDrag(PointF displacement, MotionEvent ev) { + float xProgress = Math.max(0, displacement.x) / mXRange; + float yProgress = Math.max(0, -displacement.y) / mYRange; + yProgress = Utilities.mapRange(yProgress, Y_ANIM_MIN_PROGRESS, 1f); + + boolean wasHomeScreenVisible = mIsHomeScreenVisible; + if (wasHomeScreenVisible && mNonOverviewAnim != null) { + mNonOverviewAnim.setPlayFraction(xProgress); + } + mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress) + <= 1 - ALPHA_CUTOFF_THRESHOLD; + + if (wasHomeScreenVisible && !mIsHomeScreenVisible) { + // Get the shelf all the way offscreen so it pops up when we decide to peek it. + mShelfPeekAnim.setShelfState(HIDE, LINEAR, 0); + } + + // Only allow motion pause if the home screen is invisible, since some + // home screen elements will appear in the shelf on motion pause. + mMotionPauseDetector.setDisallowPause(mIsHomeScreenVisible + || -displacement.y < mMotionPauseMinDisplacement); + mMotionPauseDetector.addPosition(displacement.y, ev.getEventTime()); + + if (mIsHomeScreenVisible) { + // Cancel the shelf anim so it doesn't clobber mNonOverviewAnim. + mShelfPeekAnim.setShelfState(CANCEL, LINEAR, 0); + } + + if (mXOverviewAnim != null) { + mXOverviewAnim.setPlayFraction(xProgress); + } + if (mYOverviewAnim != null) { + mYOverviewAnim.setPlayFraction(yProgress); + } + return true; + } + + @Override + public void onDragEnd(PointF velocity) { + boolean horizontalFling = mSwipeDetector.isFling(velocity.x); + boolean verticalFling = mSwipeDetector.isFling(velocity.y); + boolean noFling = !horizontalFling && !verticalFling; + int logAction = noFling ? Touch.SWIPE : Touch.FLING; + if (mMotionPauseDetector.isPaused() && noFling) { + cancelAnimations(); + + Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation( + INDEX_PAUSE_TO_OVERVIEW_ANIM); + overviewAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + onAnimationToStateCompleted(OVERVIEW, logAction); + } + }); + overviewAnim.start(); + return; + } + + final LauncherState targetState; + if (horizontalFling && verticalFling) { + // Flinging left and up, left and down, or right and up all go back home. + // Only flinging right and down goes to quick switch. + targetState = velocity.x < 0 || velocity.y < 0 ? NORMAL : QUICK_SWITCH; + } else if (horizontalFling) { + targetState = velocity.x > 0 ? QUICK_SWITCH : NORMAL; + } else if (verticalFling) { + targetState = velocity.y > 0 ? QUICK_SWITCH : NORMAL; + } else { + // If user isn't flinging, just snap to the closest state based on x progress. + boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f; + targetState = passedHorizontalThreshold ? QUICK_SWITCH : NORMAL; + } + + // Animate the various components to the target state. + + float xProgress = mXOverviewAnim.getProgressFraction(); + float startXProgress = Utilities.boundToRange(xProgress + + velocity.x * getSingleFrameMs(mLauncher) / mXRange, 0f, 1f); + final float endXProgress = targetState == NORMAL ? 0 : 1; + long xDuration = BaseSwipeDetector.calculateDuration(velocity.x, + Math.abs(endXProgress - startXProgress)); + ValueAnimator xOverviewAnim = mXOverviewAnim.getAnimationPlayer(); + xOverviewAnim.setFloatValues(startXProgress, endXProgress); + xOverviewAnim.setDuration(xDuration) + .setInterpolator(scrollInterpolatorForVelocity(velocity.x)); + mXOverviewAnim.dispatchOnStartWithVelocity(endXProgress, velocity.x); + + boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL; + + float yProgress = mYOverviewAnim.getProgressFraction(); + float startYProgress = Utilities.boundToRange(yProgress + - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, 1f); + final float endYProgress; + if (flingUpToNormal) { + endYProgress = 1; + } else if (targetState == NORMAL) { + // Keep overview at its current scale/translationY as it slides off the screen. + endYProgress = startYProgress; + } else { + endYProgress = 0; + } + long yDuration = BaseSwipeDetector.calculateDuration(velocity.y, + Math.abs(endYProgress - startYProgress)); + ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer(); + yOverviewAnim.setFloatValues(startYProgress, endYProgress); + yOverviewAnim.setDuration(yDuration); + mYOverviewAnim.dispatchOnStartWithVelocity(endYProgress, velocity.y); + + ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer(); + if (flingUpToNormal && !mIsHomeScreenVisible) { + // We are flinging to home while workspace is invisible, run the same staggered + // animation as from an app. + // Update mNonOverviewAnim to do nothing so it doesn't interfere. + updateNonOverviewAnim(targetState, new AnimatorSetBuilder(), 0 /* animComponents */); + nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer(); + + new StaggeredWorkspaceAnim(mLauncher, null, velocity.y, + false /* animateOverviewScrim */).start(); + } else { + boolean canceled = targetState == NORMAL; + if (canceled) { + // Let the state manager know that the animation didn't go to the target state, + // but don't clean up yet (we already clean up when the animation completes). + mNonOverviewAnim.dispatchOnCancelWithoutCancelRunnable(); + } + float startProgress = mNonOverviewAnim.getProgressFraction(); + float endProgress = canceled ? 0 : 1; + nonOverviewAnim.setFloatValues(startProgress, endProgress); + mNonOverviewAnim.dispatchOnStartWithVelocity(endProgress, + horizontalFling ? velocity.x : velocity.y); + } + + nonOverviewAnim.setDuration(Math.max(xDuration, yDuration)); + mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState, logAction)); + + cancelAnimations(); + xOverviewAnim.start(); + yOverviewAnim.start(); + nonOverviewAnim.start(); + } + + private void onAnimationToStateCompleted(LauncherState targetState, int logAction) { + mLauncher.getUserEventDispatcher().logStateChangeAction(logAction, + getDirectionForLog(), mSwipeDetector.getDownX(), mSwipeDetector.getDownY(), + LauncherLogProto.ContainerType.NAVBAR, + mStartState.containerType, + targetState.containerType, + mLauncher.getWorkspace().getCurrentPage()); + mLauncher.getStateManager().goToState(targetState, false, this::clearState); + } + + private int getDirectionForLog() { + return Utilities.isRtl(mLauncher.getResources()) ? Direction.LEFT : Direction.RIGHT; + } + + private void cancelAnimations() { + if (mNonOverviewAnim != null) { + mNonOverviewAnim.getAnimationPlayer().cancel(); + } + if (mXOverviewAnim != null) { + mXOverviewAnim.getAnimationPlayer().cancel(); + } + if (mYOverviewAnim != null) { + mYOverviewAnim.getAnimationPlayer().cancel(); + } + mShelfPeekAnim.setShelfState(ShelfAnimState.CANCEL, LINEAR, 0); + mMotionPauseDetector.clear(); + } + + private void clearState() { + cancelAnimations(); + mNonOverviewAnim = null; + mXOverviewAnim = null; + mYOverviewAnim = null; + mIsHomeScreenVisible = true; + mSwipeDetector.finishedScrolling(); + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java index 844152be28..5b6f7fe966 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -24,7 +24,6 @@ import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.launcher3.LauncherStateManager.ANIM_ALL; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.launcher3.anim.Interpolators.INSTANT; @@ -52,10 +51,8 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherInitListener; import com.android.launcher3.LauncherState; -import com.android.launcher3.LauncherStateManager; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.views.FloatingIconView; @@ -194,18 +191,8 @@ public final class LauncherActivityInterface implements BaseActivityInterfacegetOverviewPanel().getScroller().forceFinished(true); + } + /** * Starts the animation. */ diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java index 0e591cac7a..7885f5cb9b 100644 --- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java +++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java @@ -18,6 +18,7 @@ package com.android.quickstep.views; import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.LauncherState.QUICK_SWITCH; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.LINEAR; @@ -34,7 +35,9 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.view.animation.Interpolator; +import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.Interpolators; @@ -52,7 +55,8 @@ import com.android.quickstep.util.LayoutUtils; * From normal state to overview state, the shelf just fades in and does not move * From overview state to all-apps state the shelf moves up and fades in to cover the screen */ -public class ShelfScrimView extends ScrimView implements NavigationModeChangeListener { +public class ShelfScrimView extends ScrimView + implements NavigationModeChangeListener { // If the progress is more than this, shelf follows the finger, otherwise it moves faster to // cover the whole screen @@ -193,8 +197,10 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis if (mProgress >= 1) { mRemainingScreenColor = 0; mShelfColor = 0; + LauncherState state = mLauncher.getStateManager().getState(); if (mSysUINavigationMode == Mode.NO_BUTTON - && mLauncher.getStateManager().getState() == BACKGROUND_APP) { + && (state == BACKGROUND_APP || state == QUICK_SWITCH) + && mLauncher.getShelfPeekAnim().isPeeking()) { // Show the shelf background when peeking during swipe up. mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha); } diff --git a/src/com/android/launcher3/anim/AlphaUpdateListener.java b/src/com/android/launcher3/anim/AlphaUpdateListener.java index 8ac9d662cd..eabd283699 100644 --- a/src/com/android/launcher3/anim/AlphaUpdateListener.java +++ b/src/com/android/launcher3/anim/AlphaUpdateListener.java @@ -27,7 +27,7 @@ import android.view.ViewGroup; */ public class AlphaUpdateListener extends AnimationSuccessListener implements AnimatorUpdateListener { - private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f; + public static final float ALPHA_CUTOFF_THRESHOLD = 0.01f; private View mView; diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java index 2c440bba11..4a52795f89 100644 --- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java +++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java @@ -26,15 +26,15 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.util.Log; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; + import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; -import androidx.dynamicanimation.animation.DynamicAnimation; -import androidx.dynamicanimation.animation.SpringAnimation; - /** * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators * and durations. @@ -250,6 +250,17 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat } } + /** + * Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This + * is intended to be used only if you need to cancel but want to defer cleaning up yourself. + */ + public void dispatchOnCancelWithoutCancelRunnable() { + Runnable onCancel = mOnCancelRunnable; + setOnCancelRunnable(null); + dispatchOnCancel(); + setOnCancelRunnable(onCancel); + } + public void dispatchOnCancel() { dispatchOnCancelRecursively(mAnim); } @@ -283,10 +294,6 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat mOnCancelRunnable = runnable; } - public Runnable getOnCancelRunnable() { - return mOnCancelRunnable; - } - public void skipToEnd() { mSkipToEnd = true; for (SpringAnimation spring : mSprings) { diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java index c45cd85aad..fccc120900 100644 --- a/src/com/android/launcher3/anim/Interpolators.java +++ b/src/com/android/launcher3/anim/Interpolators.java @@ -39,6 +39,7 @@ public class Interpolators { public static final Interpolator LINEAR = new LinearInterpolator(); public static final Interpolator ACCEL = new AccelerateInterpolator(); + public static final Interpolator ACCEL_0_75 = new AccelerateInterpolator(0.75f); public static final Interpolator ACCEL_1_5 = new AccelerateInterpolator(1.5f); public static final Interpolator ACCEL_2 = new AccelerateInterpolator(2); @@ -48,6 +49,7 @@ public class Interpolators { public static final Interpolator DEACCEL_2 = new DecelerateInterpolator(2); public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f); public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f); + public static final Interpolator DEACCEL_5 = new DecelerateInterpolator(5f); public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator(); diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 60f6ee9c52..f40f9762d0 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -413,10 +413,7 @@ public abstract class AbstractStateChangeTouchController } else { // Let the state manager know that the animation didn't go to the target state, // but don't cancel ourselves (we already clean up when the animation completes). - Runnable onCancel = mCurrentAnimation.getOnCancelRunnable(); - mCurrentAnimation.setOnCancelRunnable(null); - mCurrentAnimation.dispatchOnCancel(); - mCurrentAnimation.setOnCancelRunnable(onCancel); + mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(); endProgress = 0; if (progress <= 0) { diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java index 9f59d78217..e64b2fb483 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -77,7 +77,7 @@ import java.util.List; /** * Simple scrim which draws a flat color */ -public class ScrimView extends View implements Insettable, OnChangeListener, +public class ScrimView extends View implements Insettable, OnChangeListener, AccessibilityStateChangeListener, StateListener { public static final Property DRAG_HANDLE_ALPHA = @@ -101,7 +101,7 @@ public class ScrimView extends View implements Insettable, OnChangeListener, private final Rect mTempRect = new Rect(); private final int[] mTempPos = new int[2]; - protected final Launcher mLauncher; + protected final T mLauncher; private final WallpaperColorInfo mWallpaperColorInfo; private final AccessibilityManager mAM; protected final int mEndScrim; @@ -130,7 +130,7 @@ public class ScrimView extends View implements Insettable, OnChangeListener, public ScrimView(Context context, AttributeSet attrs) { super(context, attrs); - mLauncher = Launcher.getLauncher(context); + mLauncher = Launcher.cast(Launcher.getLauncher(context)); mWallpaperColorInfo = WallpaperColorInfo.getInstance(context); mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor); From f9e559275dcf8cf4c59d70614b5e117d45c76c27 Mon Sep 17 00:00:00 2001 From: vadimt Date: Tue, 19 Nov 2019 11:45:57 -0800 Subject: [PATCH 06/24] Making the default launcher rule outer for the Portrait-Landscape one Portrait-Landscape uses Launcher activity and needs it to be set up correctly. Change-Id: I13c9712647b3e45a259039d7968bb95cfe8158ef --- .../launcher3/ui/AbstractLauncherUiTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index bb19515dd0..4243ed041f 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -122,10 +122,6 @@ public abstract class AbstractLauncherUiTest { protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule(); - @Rule - public ShellCommandRule mDefaultLauncherRule = - TestHelpers.isInLauncherProcess() ? ShellCommandRule.setDefaultLauncher() : null; - @Rule public ShellCommandRule mDisableHeadsUpNotification = ShellCommandRule.disableHeadsUpNotification(); @@ -154,9 +150,13 @@ public abstract class AbstractLauncherUiTest { } protected TestRule getRulesInsideActivityMonitor() { - return RuleChain. - outerRule(new PortraitLandscapeRunner(this)). - around(new FailureWatcher(mDevice)); + final RuleChain inner = RuleChain.outerRule(new PortraitLandscapeRunner(this)) + .around(new FailureWatcher(mDevice)); + + return TestHelpers.isInLauncherProcess() + ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()) + .around(inner) : + inner; } @Rule From 4744701f60b281c0df6c22f353018b723b1ae8d6 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Tue, 19 Nov 2019 14:40:12 -0800 Subject: [PATCH 07/24] Fix folder open/close animation when grid size is small. The issue was that the scaled folder size was smaller than the clip size. To ensure that the folder size is always larger than the clip size, we instead scale the contents of the folder instead of the folder itself. Bug: 143703075 Change-Id: I6eadcc487148fe7e2b7dd9f0ff94a9205048b992 --- src/com/android/launcher3/folder/Folder.java | 4 +- .../folder/FolderAnimationManager.java | 83 +++++++++++++++---- 2 files changed, 68 insertions(+), 19 deletions(-) diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 6b9e205258..0bd2c9af7f 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -148,7 +148,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo public ExtendedEditText mFolderName; private PageIndicatorDots mPageIndicator; - private View mFooter; + protected View mFooter; private int mFooterHeight; // Cell ranks used for drag and drop @@ -991,7 +991,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo lp.y = top; } - private int getContentAreaHeight() { + protected int getContentAreaHeight() { DeviceProfile grid = mLauncher.getDeviceProfile(); int maxContentAreaHeight = grid.availableHeightPx - grid.getTotalWorkspacePadding().y - mFooterHeight; diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index 9eb06938a3..1310d374ea 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -59,6 +59,8 @@ import java.util.List; */ public class FolderAnimationManager { + private static final int FOLDER_NAME_ALPHA_DURATION = 32; + private Folder mFolder; private FolderPagedView mContent; private GradientDrawable mFolderBackground; @@ -130,11 +132,19 @@ public class FolderAnimationManager { * scaleRelativeToDragLayer; final float finalScale = 1f; float scale = mIsOpening ? initialScale : finalScale; - mFolder.setScaleX(scale); - mFolder.setScaleY(scale); mFolder.setPivotX(0); mFolder.setPivotY(0); + // Scale the contents of the folder. + mFolder.mContent.setScaleX(scale); + mFolder.mContent.setScaleY(scale); + mFolder.mContent.setPivotX(0); + mFolder.mContent.setPivotY(0); + mFolder.mFooter.setScaleX(scale); + mFolder.mFooter.setScaleY(scale); + mFolder.mFooter.setPivotX(0); + mFolder.mFooter.setPivotY(0); + // We want to create a small X offset for the preview items, so that they follow their // expected path to their final locations. ie. an icon should not move right, if it's final // location is to its left. This value is arbitrarily defined. @@ -143,14 +153,13 @@ public class FolderAnimationManager { previewItemOffsetX = (int) (lp.width * initialScale - initialSize - previewItemOffsetX); } - final int paddingOffsetX = (int) ((mFolder.getPaddingLeft() + mContent.getPaddingLeft()) - * initialScale); - final int paddingOffsetY = (int) ((mFolder.getPaddingTop() + mContent.getPaddingTop()) - * initialScale); + final int paddingOffsetX = (int) (mContent.getPaddingLeft() * initialScale); + final int paddingOffsetY = (int) (mContent.getPaddingTop() * initialScale); - int initialX = folderIconPos.left + mPreviewBackground.getOffsetX() - paddingOffsetX - - previewItemOffsetX; - int initialY = folderIconPos.top + mPreviewBackground.getOffsetY() - paddingOffsetY; + int initialX = folderIconPos.left + mFolder.getPaddingLeft() + + mPreviewBackground.getOffsetX() - paddingOffsetX - previewItemOffsetX; + int initialY = folderIconPos.top + mFolder.getPaddingTop() + + mPreviewBackground.getOffsetY() - paddingOffsetY; final float xDistance = initialX - lp.x; final float yDistance = initialY - lp.y; @@ -164,11 +173,10 @@ public class FolderAnimationManager { // Set up the reveal animation that clips the Folder. int totalOffsetX = paddingOffsetX + previewItemOffsetX; - Rect startRect = new Rect( - Math.round(totalOffsetX / initialScale), - Math.round(paddingOffsetY / initialScale), - Math.round((totalOffsetX + initialSize) / initialScale), - Math.round((paddingOffsetY + initialSize) / initialScale)); + Rect startRect = new Rect(totalOffsetX, + paddingOffsetY, + Math.round((totalOffsetX + initialSize)), + Math.round((paddingOffsetY + initialSize))); Rect endRect = new Rect(0, 0, lp.width, lp.height); float finalRadius = ResourceUtils.pxFromDp(2, mContext.getResources().getDisplayMetrics()); @@ -189,17 +197,46 @@ public class FolderAnimationManager { play(a, getAnimator(mFolder, View.TRANSLATION_X, xDistance, 0f)); play(a, getAnimator(mFolder, View.TRANSLATION_Y, yDistance, 0f)); - play(a, getAnimator(mFolder, SCALE_PROPERTY, initialScale, finalScale)); + play(a, getAnimator(mFolder.mContent, SCALE_PROPERTY, initialScale, finalScale)); + play(a, getAnimator(mFolder.mFooter, SCALE_PROPERTY, initialScale, finalScale)); play(a, getAnimator(mFolderBackground, "color", initialColor, finalColor)); play(a, mFolderIcon.mFolderName.createTextAlphaAnimator(!mIsOpening)); play(a, getShape().createRevealAnimator( mFolder, startRect, endRect, finalRadius, !mIsOpening)); + // Fade in the folder name, as the text can overlap the icons when grid size is small. + mFolder.mFolderName.setAlpha(mIsOpening ? 0f : 1f); + play(a, getAnimator(mFolder.mFolderName, View.ALPHA, 0, 1), + mIsOpening ? FOLDER_NAME_ALPHA_DURATION : 0, + mIsOpening ? mDuration - FOLDER_NAME_ALPHA_DURATION : FOLDER_NAME_ALPHA_DURATION); + + // Translate the footer so that it tracks the bottom of the content. + float normalHeight = mFolder.getContentAreaHeight(); + float scaledHeight = normalHeight * initialScale; + float diff = normalHeight - scaledHeight; + play(a, getAnimator(mFolder.mFooter, View.TRANSLATION_Y, -diff, 0f)); // Animate the elevation midway so that the shadow is not noticeable in the background. int midDuration = mDuration / 2; Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0); play(a, z, mIsOpening ? midDuration : 0, midDuration); + + // Store clip variables + CellLayout cellLayout = mContent.getCurrentCellLayout(); + boolean folderClipChildren = mFolder.getClipChildren(); + boolean folderClipToPadding = mFolder.getClipToPadding(); + boolean contentClipChildren = mContent.getClipChildren(); + boolean contentClipToPadding = mContent.getClipToPadding(); + boolean cellLayoutClipChildren = cellLayout.getClipChildren(); + boolean cellLayoutClipPadding = cellLayout.getClipToPadding(); + + mFolder.setClipChildren(false); + mFolder.setClipToPadding(false); + mContent.setClipChildren(false); + mContent.setClipToPadding(false); + cellLayout.setClipChildren(false); + cellLayout.setClipToPadding(false); + a.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -207,8 +244,20 @@ public class FolderAnimationManager { mFolder.setTranslationX(0.0f); mFolder.setTranslationY(0.0f); mFolder.setTranslationZ(0.0f); - mFolder.setScaleX(1f); - mFolder.setScaleY(1f); + mFolder.mContent.setScaleX(1f); + mFolder.mContent.setScaleY(1f); + mFolder.mFooter.setScaleX(1f); + mFolder.mFooter.setScaleY(1f); + mFolder.mFooter.setTranslationX(0f); + mFolder.mFolderName.setAlpha(1f); + + mFolder.setClipChildren(folderClipChildren); + mFolder.setClipToPadding(folderClipToPadding); + mContent.setClipChildren(contentClipChildren); + mContent.setClipToPadding(contentClipToPadding); + cellLayout.setClipChildren(cellLayoutClipChildren); + cellLayout.setClipToPadding(cellLayoutClipPadding); + } }); From 83b7a6d8bfcee512542a5def54ac247adfd0adbc Mon Sep 17 00:00:00 2001 From: vadimt Date: Tue, 19 Nov 2019 15:00:29 -0800 Subject: [PATCH 08/24] Accelerating dragging to workspace by using pause at the end of the gesture Change-Id: Ib14b9519e23aa34156dab9eff75cd2609ad57e14 --- .../android/launcher3/tapl/LauncherInstrumentation.java | 7 ++++++- tests/tapl/com/android/launcher3/tapl/Workspace.java | 4 +--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 4024ff0b38..ad67f1b404 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -873,12 +873,17 @@ public final class LauncherInstrumentation { final Point start = new Point(startX, startY); final Point end = new Point(endX, endY); sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start); + final long endTime = movePointer(start, end, steps, downTime, slowDown); + sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end); + } + + long movePointer(Point start, Point end, int steps, long downTime, boolean slowDown) { long endTime = movePointer(downTime, downTime, steps * GESTURE_STEP_MS, start, end); if (slowDown) { endTime = movePointer(downTime, endTime + GESTURE_STEP_MS, 5 * GESTURE_STEP_MS, end, end); } - sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end); + return endTime; } void waitForIdle() { diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index 3301dd87d8..81d343d38b 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -39,7 +39,6 @@ import com.android.launcher3.testing.TestProtocol; * Operations on the workspace screen. */ public final class Workspace extends Home { - private static final int DRAG_DURATION = 500; private static final int FLING_STEPS = 10; private final UiObject2 mHotseat; @@ -170,8 +169,7 @@ public final class Workspace extends Home { LauncherInstrumentation.log("dragIconToWorkspace: sent down"); launcher.waitForLauncherObject(longPressIndicator); LauncherInstrumentation.log("dragIconToWorkspace: indicator"); - launcher.movePointer( - downTime, SystemClock.uptimeMillis(), DRAG_DURATION, launchableCenter, dest); + launcher.movePointer(launchableCenter, dest, 10, downTime, true); LauncherInstrumentation.log("dragIconToWorkspace: moved pointer"); launcher.sendPointer( downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest); From 74108080853f5184daaac145fa3eb4a8ed5e5be3 Mon Sep 17 00:00:00 2001 From: vadimt Date: Tue, 19 Nov 2019 18:04:13 -0800 Subject: [PATCH 09/24] Removing a workaround to make NevigationModeSwitchRule pass on CF Change-Id: I7b7cabc098f17c99406032e186a4ee363273f0a9 --- .../src/com/android/quickstep/NavigationModeSwitchRule.java | 1 - 1 file changed, 1 deletion(-) diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java index fa4c7b9fa1..5606ac28e7 100644 --- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java +++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java @@ -209,7 +209,6 @@ public class NavigationModeSwitchRule implements TestRule { final String error = mLauncher.getNavigationModeMismatchError(); assertTrue("Switching nav mode: " + error, error == null); - Thread.sleep(5000); return true; } From 09c09bba1e8e06d805eb450b21bd16223dfc2036 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 13 Nov 2019 15:38:28 -0800 Subject: [PATCH 10/24] Fix recents scale sometimes lagging behind window scale We were previously offsetting the launcher animation progress based on when we got onGestureStart, which meant it would lag behind if onGestureStart came late. Now that we track the window instead of the launcher shelf, and we don't show the launcher animation right away in fully gestural mode anyway, we should remove this to ensure the launcher part of the animation always lines up with the app window. We also reapply state whenever predictions are enabled, e.g. when launcher starts after being force stopped, and previously this was canceling the existing state animation. We don't want to do that because predictions can be enabled at any point on a cold start, and cancelling the existing state animation means that RecentsView shows up in fullscreen and not attached to the app window for the duration of the gesture. Bug: 144454486 Change-Id: I65a2c71c9acd2f5345941ea2cff7d32c04b7be3f Merged-In: I65a2c71c9acd2f5345941ea2cff7d32c04b7be3f --- .../android/launcher3/appprediction/PredictionRowView.java | 2 +- .../com/android/quickstep/WindowTransformSwipeHandler.java | 7 +------ src/com/android/launcher3/Launcher.java | 6 +++++- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java index 23db5df2ed..f82af62aac 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java @@ -276,7 +276,7 @@ public class PredictionRowView extends LinearLayout implements boolean predictionsEnabled = predictionCount > 0; if (predictionsEnabled != mPredictionsEnabled) { mPredictionsEnabled = predictionsEnabled; - mLauncher.reapplyUi(); + mLauncher.reapplyUi(false /* cancelCurrentAnimation */); updateVisibility(); } mParent.onHeightUpdated(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java index c80dede537..ff134b1f2a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -206,8 +206,6 @@ public class WindowTransformSwipeHandler private boolean mIsShelfPeeking; private boolean mContinuingLastGesture; - // To avoid UI jump when gesture is started, we offset the animation by the threshold. - private float mShiftAtGestureStart = 0; private ThumbnailData mTaskSnapshot; @@ -580,9 +578,7 @@ public class WindowTransformSwipeHandler // Normalize the progress to 0 to 1, as the animation controller will clamp it to that // anyway. The controller mimics the drag length factor by applying it to its interpolators. float progress = mCurrentShift.value / mDragLengthFactor; - mLauncherTransitionController.setPlayFraction( - progress <= mShiftAtGestureStart || mShiftAtGestureStart >= 1 - ? 0 : (progress - mShiftAtGestureStart) / (1 - mShiftAtGestureStart)); + mLauncherTransitionController.setPlayFraction(progress); } /** @@ -622,7 +618,6 @@ public class WindowTransformSwipeHandler @Override public void onGestureStarted() { notifyGestureStartedAsync(); - mShiftAtGestureStart = mCurrentShift.value; setStateOnUiThread(STATE_GESTURE_STARTED); mGestureStarted = true; } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 7bb618ddfc..9bf690f13f 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -445,12 +445,16 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @Override public void reapplyUi() { + reapplyUi(true /* cancelCurrentAnimation */); + } + + public void reapplyUi(boolean cancelCurrentAnimation) { if (supportsFakeLandscapeUI()) { mRotationMode = mStableDeviceProfile == null ? RotationMode.NORMAL : UiFactory.getRotationMode(mDeviceProfile); } getRootView().dispatchInsets(); - getStateManager().reapplyState(true /* cancelCurrentAnimation */); + getStateManager().reapplyState(cancelCurrentAnimation); } @Override From 94fb4cd3fd561aa08ab1d0970687981387d7512a Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 20 Nov 2019 13:08:44 -0800 Subject: [PATCH 11/24] Fix quick switch from home biased towards returning home When flinging up and to the right, we previously always returned home. Now, if the right velocity is stronger, we quick switch. Bug: 126596417 Change-Id: I14fa0584399bb90f2e07e0b296fc5932d8224fbf --- .../NoButtonQuickSwitchTouchController.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index 7ffffeda10..7dec014f91 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -342,9 +342,20 @@ public class NoButtonQuickSwitchTouchController implements TouchController, final LauncherState targetState; if (horizontalFling && verticalFling) { - // Flinging left and up, left and down, or right and up all go back home. - // Only flinging right and down goes to quick switch. - targetState = velocity.x < 0 || velocity.y < 0 ? NORMAL : QUICK_SWITCH; + if (velocity.x < 0) { + // Flinging left and up or down both go back home. + targetState = NORMAL; + } else { + if (velocity.y > 0) { + // Flinging right and down goes to quick switch. + targetState = QUICK_SWITCH; + } else { + // Flinging up and right could go either home or to quick switch. + // Determine the target based on the higher velocity. + targetState = Math.abs(velocity.x) > Math.abs(velocity.y) + ? QUICK_SWITCH : NORMAL; + } + } } else if (horizontalFling) { targetState = velocity.x > 0 ? QUICK_SWITCH : NORMAL; } else if (verticalFling) { From ad7e30f5d7d7235e4f98632d806043421eb7d15a Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Wed, 20 Nov 2019 13:17:51 -0800 Subject: [PATCH 12/24] Fix bug where icon remains invisible after returning home. - The bug is caused by cancelling of the RectFSpringAnim before the StaggeredWorkspaceAnim has started. - Instead of having logic in StaggeredWorkspaceAnim control the visibility of the icon, we instead maintain all the visibility within the FloatingIconView class itself. Bug: 142120338 Change-Id: I082291ca9b288f57701cc00d61a9b3a84da8b084 --- .../NoButtonQuickSwitchTouchController.java | 4 +- .../quickstep/LauncherActivityInterface.java | 4 +- .../util/StaggeredWorkspaceAnim.java | 38 +------------------ .../launcher3/views/FloatingIconView.java | 27 +++++++------ 4 files changed, 21 insertions(+), 52 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index 54d165f5bd..9ad063837d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -395,8 +395,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController, updateNonOverviewAnim(targetState, new AnimatorSetBuilder(), 0 /* animComponents */); nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer(); - new StaggeredWorkspaceAnim(mLauncher, null, velocity.y, - false /* animateOverviewScrim */).start(); + new StaggeredWorkspaceAnim(mLauncher, velocity.y, false /* animateOverviewScrim */) + .start(); } else { boolean canceled = targetState == NORMAL; if (canceled) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java index 5b6f7fe966..28fc3dadd5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -191,8 +191,8 @@ public final class LauncherActivityInterface implements BaseActivityInterface { if (cancellationSignal.isCanceled()) { @@ -585,10 +583,7 @@ public class FloatingIconView extends View implements mIconLoadResult.iconOffset); setVisibility(VISIBLE); - if (isOpening) { - // Delay swapping views until the icon is loaded to prevent a flash. - hideOriginalView(originalView); - } + hideOriginalView(originalView); }; mLoadIconSignal = cancellationSignal; } @@ -596,9 +591,9 @@ public class FloatingIconView extends View implements } private void hideOriginalView(View originalView) { - if (originalView instanceof BubbleTextView) { - ((BubbleTextView) originalView).setIconVisible(false); - ((BubbleTextView) originalView).setForceHideDot(true); + if (originalView instanceof IconLabelDotView) { + ((IconLabelDotView) originalView).setIconVisible(false); + ((IconLabelDotView) originalView).setForceHideDot(true); } else { originalView.setVisibility(INVISIBLE); } @@ -674,6 +669,9 @@ public class FloatingIconView extends View implements } public void fastFinish() { + if (mLoadIconSignal != null) { + mLoadIconSignal.cancel(); + } if (mEndRunnable != null) { mEndRunnable.run(); mEndRunnable = null; @@ -689,6 +687,10 @@ public class FloatingIconView extends View implements if (mIconLoadResult != null && mIconLoadResult.isIconLoaded) { setVisibility(View.VISIBLE); } + if (!mIsOpening) { + // When closing an app, we want the item on the workspace to be invisible immediately + hideOriginalView(mOriginalIcon); + } } @Override @@ -798,7 +800,7 @@ public class FloatingIconView extends View implements // Must be called after the fastFinish listener and end runnable is created so that // the icon is not left in a hidden state. if (shouldLoadIcon) { - view.checkIconResult(originalView, isOpening); + view.checkIconResult(originalView); } return view; @@ -842,6 +844,7 @@ public class FloatingIconView extends View implements @Override public void onAnimationStart(Animator animation) { btv.setIconVisible(true); + btv.setForceHideDot(true); } }); fade.play(ObjectAnimator.ofInt(btv.getIcon(), DRAWABLE_ALPHA, 0, 255)); From 5e1ecf6d7309a5a2dcda1715a7373e504eb5384a Mon Sep 17 00:00:00 2001 From: thiruram Date: Wed, 13 Nov 2019 17:49:35 -0800 Subject: [PATCH 13/24] Adds proto-lite libraries to Launcher3 make files. Change-Id: Icb2f8fcdb4d5344de835f4702fc01ec80d4a3b84 --- Android.bp | 16 ++++++++++++++++ tests/Android.mk | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Android.bp b/Android.bp index 4c38205979..fc998809e1 100644 --- a/Android.bp +++ b/Android.bp @@ -30,3 +30,19 @@ android_library { manifest: "tests/tapl/AndroidManifest.xml", platform_apis: true, } + +java_library_static { + name: "launcher-log-protos-lite", + srcs: [ + "protos/*.proto", + "proto_overrides/*.proto", + ], + sdk_version: "current", + proto: { + type: "lite", + local_include_dirs:[ + "protos", + "proto_overrides", + ], + }, +} diff --git a/tests/Android.mk b/tests/Android.mk index 31a996084c..83fdddce28 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -50,7 +50,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ androidx.test.runner \ androidx.test.rules \ androidx.test.uiautomator_uiautomator \ - mockito-target-minus-junit4 + mockito-target-minus-junit4 \ + launcher-log-protos-lite ifneq (,$(wildcard frameworks/base)) LOCAL_PRIVATE_PLATFORM_APIS := true From a3ea27de3941f2802361aff1d46547122f096c7d Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Wed, 20 Nov 2019 12:31:15 -0800 Subject: [PATCH 14/24] Fix bug where icon remains invisible after returning home. - The bug is caused by cancelling of the RectFSpringAnim before the StaggeredWorkspaceAnim has started. - Instead of having logic in StaggeredWorkspaceAnim control the visibility of the icon, we instead maintain all the visibility within the FloatingIconView class itself. Bug: 142120338 Change-Id: I94f3a066d395f9c3b97dc6ee9fc836e9401650a5 Merged-In: I082291ca9b288f57701cc00d61a9b3a84da8b084 --- .../NoButtonQuickSwitchTouchController.java | 4 +- .../LauncherActivityControllerHelper.java | 4 +- .../util/StaggeredWorkspaceAnim.java | 38 +------------------ .../launcher3/views/FloatingIconView.java | 28 +++++++------- 4 files changed, 21 insertions(+), 53 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index 7ffffeda10..86aa43044c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -398,8 +398,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController, updateNonOverviewAnim(targetState, new AnimatorSetBuilder(), 0 /* animComponents */); nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer(); - new StaggeredWorkspaceAnim(mLauncher, null, velocity.y, - false /* animateOverviewScrim */).start(); + new StaggeredWorkspaceAnim(mLauncher, velocity.y, false /* animateOverviewScrim */) + .start(); } else { boolean canceled = targetState == NORMAL; if (canceled) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java index eefde12860..c0be9ec3e8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -166,8 +166,8 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe @Override public void playAtomicAnimation(float velocity) { - new StaggeredWorkspaceAnim(activity, workspaceView, velocity, - true /* animateOverviewScrim */).start(); + new StaggeredWorkspaceAnim(activity, velocity, true /* animateOverviewScrim */) + .start(); } }; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index 5f02de2b9c..958ef7d4f1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -28,8 +28,6 @@ import android.animation.ObjectAnimator; import android.view.View; import android.view.ViewGroup; -import androidx.annotation.Nullable; - import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; @@ -43,7 +41,6 @@ import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.anim.SpringObjectAnimator; import com.android.launcher3.graphics.OverviewScrim; -import com.android.launcher3.views.IconLabelDotView; import com.android.quickstep.views.RecentsView; import java.util.ArrayList; @@ -67,20 +64,12 @@ public class StaggeredWorkspaceAnim { private final float mVelocity; private final float mSpringTransY; - // The original view of the {@link FloatingIconView}. - private final View mOriginalView; - private final List mAnimators = new ArrayList<>(); - /** - * @param floatingViewOriginalView The FloatingIconView's original view. - */ - public StaggeredWorkspaceAnim(Launcher launcher, @Nullable View floatingViewOriginalView, - float velocity, boolean animateOverviewScrim) { + public StaggeredWorkspaceAnim(Launcher launcher, float velocity, boolean animateOverviewScrim) { prepareToAnimate(launcher); mVelocity = velocity; - mOriginalView = floatingViewOriginalView; // Scale the translationY based on the initial velocity to better sync the workspace items // with the floating view. @@ -212,35 +201,12 @@ public class StaggeredWorkspaceAnim { springTransY.setStartDelay(startDelay); mAnimators.add(springTransY); - ObjectAnimator alpha = getAlphaAnimator(v, startDelay); - if (v == mOriginalView) { - // For IconLabelDotViews, we just want the label to fade in. - // Icon, badge, and dots will animate in separately (controlled via FloatingIconView) - if (v instanceof IconLabelDotView) { - alpha.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - IconLabelDotView view = (IconLabelDotView) v; - view.setIconVisible(false); - view.setForceHideDot(true); - } - }); - } else { - return; - } - } - v.setAlpha(0); - mAnimators.add(alpha); - } - - private ObjectAnimator getAlphaAnimator(View v, long startDelay) { ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f); alpha.setInterpolator(LINEAR); alpha.setDuration(ALPHA_DURATION_MS); alpha.setStartDelay(startDelay); - return alpha; - + mAnimators.add(alpha); } private void addScrimAnimationForState(Launcher launcher, LauncherState state, long duration) { diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index 45c0d90971..49d94f06eb 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -560,7 +560,7 @@ public class FloatingIconView extends View implements * Checks if the icon result is loaded. If true, we set the icon immediately. Else, we add a * callback to set the icon once the icon result is loaded. */ - private void checkIconResult(View originalView, boolean isOpening) { + private void checkIconResult(View originalView) { CancellationSignal cancellationSignal = new CancellationSignal(); if (mIconLoadResult == null) { @@ -572,9 +572,7 @@ public class FloatingIconView extends View implements if (mIconLoadResult.isIconLoaded) { setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge, mIconLoadResult.iconOffset); - if (isOpening) { - hideOriginalView(originalView); - } + hideOriginalView(originalView); } else { mIconLoadResult.onIconLoaded = () -> { if (cancellationSignal.isCanceled()) { @@ -583,12 +581,8 @@ public class FloatingIconView extends View implements setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge, mIconLoadResult.iconOffset); - setVisibility(VISIBLE); - if (isOpening) { - // Delay swapping views until the icon is loaded to prevent a flash. - hideOriginalView(originalView); - } + hideOriginalView(originalView); }; mLoadIconSignal = cancellationSignal; } @@ -596,9 +590,9 @@ public class FloatingIconView extends View implements } private void hideOriginalView(View originalView) { - if (originalView instanceof BubbleTextView) { - ((BubbleTextView) originalView).setIconVisible(false); - ((BubbleTextView) originalView).setForceHideDot(true); + if (originalView instanceof IconLabelDotView) { + ((IconLabelDotView) originalView).setIconVisible(false); + ((IconLabelDotView) originalView).setForceHideDot(true); } else { originalView.setVisibility(INVISIBLE); } @@ -674,6 +668,9 @@ public class FloatingIconView extends View implements } public void fastFinish() { + if (mLoadIconSignal != null) { + mLoadIconSignal.cancel(); + } if (mEndRunnable != null) { mEndRunnable.run(); mEndRunnable = null; @@ -689,6 +686,10 @@ public class FloatingIconView extends View implements if (mIconLoadResult != null && mIconLoadResult.isIconLoaded) { setVisibility(View.VISIBLE); } + if (!mIsOpening) { + // When closing an app, we want the item on the workspace to be invisible immediately + hideOriginalView(mOriginalIcon); + } } @Override @@ -798,7 +799,7 @@ public class FloatingIconView extends View implements // Must be called after the fastFinish listener and end runnable is created so that // the icon is not left in a hidden state. if (shouldLoadIcon) { - view.checkIconResult(originalView, isOpening); + view.checkIconResult(originalView); } return view; @@ -842,6 +843,7 @@ public class FloatingIconView extends View implements @Override public void onAnimationStart(Animator animation) { btv.setIconVisible(true); + btv.setForceHideDot(true); } }); fade.play(ObjectAnimator.ofInt(btv.getIcon(), DRAWABLE_ALPHA, 0, 255)); From 7d6e7add9727eae20b536fe62685131ede76df3c Mon Sep 17 00:00:00 2001 From: vadimt Date: Thu, 21 Nov 2019 19:12:19 -0800 Subject: [PATCH 15/24] Improving error messages when swiping up to home Change-Id: I7c230d657eacb7a386a906e1f5470a15e1fe5868 --- .../launcher3/tapl/LauncherInstrumentation.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 4024ff0b38..2037e3032c 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -560,10 +560,12 @@ public final class LauncherInstrumentation { final int finalState = mDevice.hasObject(By.pkg(getLauncherPackageName())) ? NORMAL_STATE_ORDINAL : BACKGROUND_APP_STATE_ORDINAL; - swipeToState( - displaySize.x / 2, displaySize.y - 1, - displaySize.x / 2, 0, - ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, finalState); + try (LauncherInstrumentation.Closable c = addContextLayer(action)) { + swipeToState( + displaySize.x / 2, displaySize.y - 1, + displaySize.x / 2, 0, + ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, finalState); + } } } else { log(action = "clicking home button"); From 5fcd8f964748b5a6fcae92ea81465d595356f203 Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Wed, 6 Nov 2019 21:34:36 -0800 Subject: [PATCH 16/24] Provide up to 3 folder name suggestions to active IME Bug: 144973594 Change-Id: I92078fffd68a1230fd87d2387404c8a2fed55bc0 --- .../android/launcher3/ExtendedEditText.java | 34 +++++++++++++++---- src/com/android/launcher3/folder/Folder.java | 18 +++++----- .../android/launcher3/folder/FolderIcon.java | 27 ++++++++++----- .../launcher3/folder/FolderNameProvider.java | 6 ++++ 4 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java index 4e0f2e7446..52a393ff02 100644 --- a/src/com/android/launcher3/ExtendedEditText.java +++ b/src/com/android/launcher3/ExtendedEditText.java @@ -21,14 +21,19 @@ import android.util.AttributeSet; import android.view.DragEvent; import android.view.KeyEvent; import android.view.View; +import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; +import com.android.launcher3.folder.FolderNameProvider; import com.android.launcher3.util.UiThreadHelper; +import java.util.List; + /** * The edit text that reports back when the back key has been pressed. + * Note: AppCompatEditText doesn't fully support #displayCompletions and #onCommitCompletion */ public class ExtendedEditText extends EditText { @@ -85,12 +90,9 @@ public class ExtendedEditText extends EditText { super.onLayout(changed, left, top, right, bottom); if (mShowImeAfterFirstLayout) { // soft input only shows one frame after the layout of the EditText happens, - post(new Runnable() { - @Override - public void run() { - showSoftInput(); - mShowImeAfterFirstLayout = false; - } + post(() -> { + showSoftInput(); + mShowImeAfterFirstLayout = false; }); } } @@ -103,9 +105,27 @@ public class ExtendedEditText extends EditText { UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken()); } + @Override + public void onCommitCompletion(CompletionInfo text) { + setText(text.getText()); + } + + /** + * Currently only used for folder name suggestion. + */ + public void displayCompletions(List suggestList) { + int cnt = Math.min(suggestList.size(), FolderNameProvider.SUGGEST_MAX); + CompletionInfo[] cInfo = new CompletionInfo[cnt]; + for (int i = 0; i < cnt; i++) { + cInfo[i] = new CompletionInfo(i, i, suggestList.get(i)); + } + post(() -> getContext().getSystemService(InputMethodManager.class) + .displayCompletions(this, cInfo)); + } + private boolean showSoftInput() { return requestFocus() && - ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE)) + getContext().getSystemService(InputMethodManager.class) .showSoftInput(this, InputMethodManager.SHOW_IMPLICIT); } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 0bd2c9af7f..33da582293 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -84,6 +84,7 @@ import com.android.launcher3.views.ClipPathView; import com.android.launcher3.widget.PendingAddShortcutInfo; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -217,7 +218,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT & ~InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS); - mFolderName.forceDisableSuggestions(true); + mFolderName.forceDisableSuggestions(!FeatureFlags.FOLDER_NAME_SUGGEST.get()); mFooter = findViewById(R.id.folder_footer); @@ -412,19 +413,20 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo }); } - /** * Show suggested folder title. */ - public void showSuggestedTitle(CharSequence suggestName) { + public void showSuggestedTitle(String[] suggestName) { if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && mInfo.contents.size() == 2) { - if (!TextUtils.isEmpty(suggestName)) { - mFolderName.setHint(suggestName); - mFolderName.setText(suggestName); + if (suggestName.length > 0 && !TextUtils.isEmpty(suggestName[0])) { + mFolderName.setHint(suggestName[0]); + mFolderName.setText(suggestName[0]); + mInfo.title = suggestName[0]; + animateOpen(); mFolderName.showKeyboard(); - mInfo.title = suggestName; + mFolderName.displayCompletions( + Arrays.asList(suggestName).subList(1, suggestName.length)); } - animateOpen(); } } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index fd6d1e38a9..7bbd45dac3 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -371,22 +371,31 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true); final int finalIndex = index; - String[] suggestedNameOut = new String[1]; + String[] suggestedNameOut = new String[FolderNameProvider.SUGGEST_MAX]; if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) { - Executors.UI_HELPER_EXECUTOR.post(() -> mLauncher.getFolderNameProvider() - .getSuggestedFolderName(getContext(), mInfo.contents, suggestedNameOut)); + Executors.UI_HELPER_EXECUTOR.post(() -> { + mLauncher.getFolderNameProvider().getSuggestedFolderName( + getContext(), mInfo.contents, suggestedNameOut); + showFinalView(finalIndex, item, suggestedNameOut); + }); + } else { + showFinalView(finalIndex, item, suggestedNameOut); } - postDelayed(() -> { - mPreviewItemManager.hidePreviewItem(finalIndex, false); - mFolder.showItem(item); - invalidate(); - mFolder.showSuggestedTitle(suggestedNameOut[0]); - }, DROP_IN_ANIMATION_DURATION); } else { addItem(item); } } + private void showFinalView(int finalIndex, final WorkspaceItemInfo item, + String[] suggestedNameOut) { + postDelayed(() -> { + mPreviewItemManager.hidePreviewItem(finalIndex, false); + mFolder.showItem(item); + invalidate(); + mFolder.showSuggestedTitle(suggestedNameOut); + }, DROP_IN_ANIMATION_DURATION); + } + public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) { WorkspaceItemInfo item; if (d.dragInfo instanceof AppInfo) { diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java index 0a1221e696..37aa815771 100644 --- a/src/com/android/launcher3/folder/FolderNameProvider.java +++ b/src/com/android/launcher3/folder/FolderNameProvider.java @@ -28,6 +28,12 @@ import java.util.ArrayList; */ public class FolderNameProvider { + /** + * IME usually has up to 3 suggest slots. Adding one as in Launcher, there are folder + * name edit box that we can also provide suggestion. + */ + public static final int SUGGEST_MAX = 4; + /** * Returns suggested folder name. */ From b2049196b977b22244f4aa5e2f4265da6f123e0b Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Fri, 22 Nov 2019 14:43:20 -0800 Subject: [PATCH 17/24] Align badging logic with platform IconDrawableFactory. The current logic in BaseIconFactory is only trying to add the user badge if the user passed in isn't null and isn't the current user, then if no such attempt is made, try to add an instant app badge. This is causing user badge to disappear if the current user is a work profile. Meanwhile, the badging logic in IconDrawableFactory is to try to add the instant app badge first, and then always try to add the user badge. Bug: 132625654 Test: manually test as in b/128625591 Change-Id: I2241bdb524ca6bc66e545a0ec8433119e0448358 Signed-off-by: Hyunyoung Song --- .../launcher3/icons/BaseIconFactory.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java index 8bd9dba1f9..bce4e0f330 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java @@ -173,25 +173,22 @@ public class BaseIconFactory implements AutoCloseable { mCanvas.setBitmap(null); } - final Bitmap result; - if (user != null && !Process.myUserHandle().equals(user)) { + if (isInstantApp) { + badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge)); + } + if (user != null) { BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap); Drawable badged = mPm.getUserBadgedIcon(drawable, user); if (badged instanceof BitmapDrawable) { - result = ((BitmapDrawable) badged).getBitmap(); + bitmap = ((BitmapDrawable) badged).getBitmap(); } else { - result = createIconBitmap(badged, 1f); + bitmap = createIconBitmap(badged, 1f); } - } else if (isInstantApp) { - badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge)); - result = bitmap; - } else { - result = bitmap; } - int color = extractColor(result); + int color = extractColor(bitmap); return icon instanceof BitmapInfo.Extender - ? ((BitmapInfo.Extender) icon).getExtendedInfo(result, color, this) - : BitmapInfo.of(result, color); + ? ((BitmapInfo.Extender) icon).getExtendedInfo(bitmap, color, this) + : BitmapInfo.of(bitmap, color); } public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) { From f58ba9ca79802f14d402fdef22f6319437e42e3b Mon Sep 17 00:00:00 2001 From: Hai Zhang Date: Mon, 20 May 2019 11:22:46 -0700 Subject: [PATCH 18/24] Align badging logic with platform IconDrawableFactory. The current logic in BaseIconFactory is only trying to add the user badge if the user passed in isn't null and isn't the current user, then if no such attempt is made, try to add an instant app badge. This is causing user badge to disappear if the current user is a work profile. Meanwhile, the badging logic in IconDrawableFactory is to try to add the instant app badge first, and then always try to add the user badge. Bug: 132625654 Test: manually test as in b/128625591 Change-Id: Ia5b456f1f27095d546504266a1a90034105c9ae9 Merged-In: I2241bdb524ca6bc66e545a0ec8433119e0448358 (cherry picked from commit bdb0236125922a3a9b20fbaa8ecd1d2477b48ed5) --- .../launcher3/icons/BaseIconFactory.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java index f491ed73f4..5c4f37ca98 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java @@ -171,22 +171,19 @@ public class BaseIconFactory implements AutoCloseable { mCanvas.setBitmap(null); } - final Bitmap result; - if (user != null && !Process.myUserHandle().equals(user)) { + if (isInstantApp) { + badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge)); + } + if (user != null) { BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap); Drawable badged = mPm.getUserBadgedIcon(drawable, user); if (badged instanceof BitmapDrawable) { - result = ((BitmapDrawable) badged).getBitmap(); + bitmap = ((BitmapDrawable) badged).getBitmap(); } else { - result = createIconBitmap(badged, 1f); + bitmap = createIconBitmap(badged, 1f); } - } else if (isInstantApp) { - badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge)); - result = bitmap; - } else { - result = bitmap; } - return BitmapInfo.fromBitmap(result, mDisableColorExtractor ? null : mColorExtractor); + return BitmapInfo.fromBitmap(bitmap, mDisableColorExtractor ? null : mColorExtractor); } public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) { From 435b11b95fd83901e7c2dc36269442b3c1312c95 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Fri, 22 Nov 2019 16:06:49 -0800 Subject: [PATCH 19/24] Don't removePluginListener() unless user is unlocked This just crashes since we can't access SharedPreferences, and we don't addPluginListener until onUserUnlocked() anyway. Change-Id: I705498f859857a52a2cb5735201a652973b26d0b --- .../src/com/android/quickstep/TouchInteractionService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 71f8ba4b0f..07537ed7db 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -391,12 +391,11 @@ public class TouchInteractionService extends Service implements PluginListener Date: Mon, 18 Nov 2019 17:12:46 -0800 Subject: [PATCH 20/24] Hybrid hotseat predicted icon visuals Bug:142753423 Test: Manual Change-Id: I6f056aaec905c8ca357b7cf78a657cdaac84e2f1 --- .../res/layout/predicted_app_icon.xml | 17 ++ .../HotseatPredictionController.java | 122 +++++++----- .../uioverrides/PredictedAppIcon.java | 188 ++++++++++++++++++ src/com/android/launcher3/CellLayout.java | 80 +++++--- .../launcher3/folder/PreviewBackground.java | 37 +++- 5 files changed, 357 insertions(+), 87 deletions(-) create mode 100644 quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml create mode 100644 quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml b/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml new file mode 100644 index 0000000000..70a765a562 --- /dev/null +++ b/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml @@ -0,0 +1,17 @@ + + + + diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java index fe9fd604e2..17a3d9185a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java @@ -18,6 +18,7 @@ package com.android.launcher3; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import android.animation.Animator; +import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionManager; @@ -40,10 +41,9 @@ import com.android.launcher3.appprediction.DynamicItemCache; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.icons.IconCache; -import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.shortcuts.ShortcutKey; -import com.android.launcher3.touch.ItemLongClickListener; +import com.android.launcher3.uioverrides.PredictedAppIcon; import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.ComponentKey; @@ -81,12 +81,14 @@ public class HotseatPredictionController implements DragController.DragListener, private AppPredictor mAppPredictor; private AllAppsStore mAllAppsStore; + private List mOutlineDrawings = new ArrayList<>(); + public HotseatPredictionController(Launcher launcher) { mLauncher = launcher; mHotseat = launcher.getHotseat(); mAllAppsStore = mLauncher.getAppsView().getAppsStore(); mAllAppsStore.addUpdateListener(this); - mDynamicItemCache = new DynamicItemCache(mLauncher, () -> fillGapsWithPrediction(false)); + mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction); mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons; launcher.getDeviceProfile().inv.addOnChangeListener(this); mHotseat.addOnAttachStateChangeListener(this); @@ -102,16 +104,17 @@ public class HotseatPredictionController implements DragController.DragListener, mLauncher.getDragController().removeDragListener(this); } - /** - * Fills gaps in the hotseat with predictions - */ - public void fillGapsWithPrediction(boolean animate) { + private void fillGapsWithPrediction() { + fillGapsWithPrediction(false, null); + } + + private void fillGapsWithPrediction(boolean animate, Runnable callback) { if (mDragObject != null) { return; } List predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers); int predictionIndex = 0; - ArrayList newItemsToAdd = new ArrayList<>(); + ArrayList newItems = new ArrayList<>(); for (int rank = 0; rank < mHotSeatItemsCount; rank++) { View child = mHotseat.getChildAt( mHotseat.getCellXFromOrder(rank), @@ -130,21 +133,37 @@ public class HotseatPredictionController implements DragController.DragListener, WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++); if (isPredictedIcon(child)) { - BubbleTextView icon = (BubbleTextView) child; + PredictedAppIcon icon = (PredictedAppIcon) child; icon.applyFromWorkspaceItem(predictedItem); + icon.finishBinding(); } else { - newItemsToAdd.add(predictedItem); + newItems.add(predictedItem); } preparePredictionInfo(predictedItem, rank); } - mLauncher.bindItems(newItemsToAdd, animate); - for (BubbleTextView icon : getPredictedIcons()) { - icon.verifyHighRes(); - icon.setOnLongClickListener((v) -> { - PopupContainerWithArrow.showForIcon((BubbleTextView) v); - return true; + bindItems(newItems, animate, callback); + } + + private void bindItems(List itemsToAdd, boolean animate, Runnable callback) { + AnimatorSet animationSet = new AnimatorSet(); + for (WorkspaceItemInfo item : itemsToAdd) { + PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item); + mLauncher.getWorkspace().addInScreenFromBind(icon, item); + icon.finishBinding(); + if (animate) { + animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1)); + } + } + if (animate) { + animationSet.addListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + if (callback != null) callback.run(); + } }); - icon.setBackgroundResource(R.drawable.predicted_icon_background); + animationSet.start(); + } else { + if (callback != null) callback.run(); } } @@ -179,6 +198,7 @@ public class HotseatPredictionController implements DragController.DragListener, .build()); mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(), this::setPredictedApps); + mAppPredictor.requestPredictionUpdate(); } @@ -210,7 +230,7 @@ public class HotseatPredictionController implements DragController.DragListener, mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache)); } updateDependencies(); - fillGapsWithPrediction(false); + fillGapsWithPrediction(); } private void updateDependencies() { @@ -219,7 +239,7 @@ public class HotseatPredictionController implements DragController.DragListener, } private void pinPrediction(ItemInfo info) { - BubbleTextView icon = (BubbleTextView) mHotseat.getChildAt( + PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt( mHotseat.getCellXFromOrder(info.rank), mHotseat.getCellYFromOrder(info.rank)); if (icon == null) { @@ -230,9 +250,7 @@ public class HotseatPredictionController implements DragController.DragListener, LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId, workspaceItemInfo.cellX, workspaceItemInfo.cellY); ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start(); - icon.reset(); - icon.applyFromWorkspaceItem(workspaceItemInfo); - icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE); + icon.pin(workspaceItemInfo); AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo); notifyItemAction(appTarget, AppTargetEvent.ACTION_PIN); } @@ -265,37 +283,35 @@ public class HotseatPredictionController implements DragController.DragListener, return predictedApps; } - private List getPredictedIcons() { - List icons = new ArrayList<>(); + private List getPredictedIcons() { + List icons = new ArrayList<>(); ViewGroup vg = mHotseat.getShortcutsAndWidgets(); for (int i = 0; i < vg.getChildCount(); i++) { View child = vg.getChildAt(i); if (isPredictedIcon(child)) { - icons.add((BubbleTextView) child); + icons.add((PredictedAppIcon) child); } } return icons; } - private void removePredictedApps(boolean animate) { - for (BubbleTextView icon : getPredictedIcons()) { - if (animate) { - icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() { - @Override - public void onAnimationSuccess(Animator animator) { - if (icon.getParent() != null) { - mHotseat.removeView(icon); - } + private void removePredictedApps(List outlines) { + for (PredictedAppIcon icon : getPredictedIcons()) { + int rank = ((WorkspaceItemInfo) icon.getTag()).rank; + outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing( + mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon)); + icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + if (icon.getParent() != null) { + mHotseat.removeView(icon); } - }); - } else { - if (icon.getParent() != null) { - mHotseat.removeView(icon); } - } + }); } } + private void notifyItemAction(AppTarget target, int action) { if (mAppPredictor != null) { mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target, action).build()); @@ -304,8 +320,13 @@ public class HotseatPredictionController implements DragController.DragListener, @Override public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { - removePredictedApps(true); + removePredictedApps(mOutlineDrawings); mDragObject = dragObject; + if (mOutlineDrawings.isEmpty()) return; + for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) { + mHotseat.addDelegatedCellDrawing(outlineDrawing); + } + mHotseat.invalidate(); } @Override @@ -322,7 +343,14 @@ public class HotseatPredictionController implements DragController.DragListener, } } mDragObject = null; - fillGapsWithPrediction(true); + fillGapsWithPrediction(true, () -> { + if (mOutlineDrawings.isEmpty()) return; + for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) { + mHotseat.removeDelegatedCellDrawing(outlineDrawing); + } + mHotseat.invalidate(); + mOutlineDrawings.clear(); + }); } @Nullable @@ -351,8 +379,7 @@ public class HotseatPredictionController implements DragController.DragListener, @Override public void onAppsUpdated() { - updateDependencies(); - fillGapsWithPrediction(false); + fillGapsWithPrediction(); } @Override @@ -375,7 +402,7 @@ public class HotseatPredictionController implements DragController.DragListener, } private static boolean isPredictedIcon(View view) { - return view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo + return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo && ((WorkspaceItemInfo) view.getTag()).container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; } @@ -396,9 +423,8 @@ public class HotseatPredictionController implements DragController.DragListener, private static AppTarget getAppTargetFromItemInfo(ItemInfo info) { if (info.getTargetComponent() == null) return null; - return new AppTarget.Builder( - new AppTargetId("app:" + info.getTargetComponent().getPackageName()), - info.getTargetComponent().getPackageName(), info.user).setClassName( - info.getTargetComponent().getClassName()).build(); + ComponentName cn = info.getTargetComponent(); + return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()), + cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java new file mode 100644 index 0000000000..e41c75a2c2 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java @@ -0,0 +1,188 @@ +/* + * 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.uioverrides; + +import static com.android.launcher3.graphics.IconShape.getShape; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.DashPathEffect; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.core.graphics.ColorUtils; + +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.CellLayout; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.graphics.IconPalette; +import com.android.launcher3.icons.IconNormalizer; +import com.android.launcher3.popup.PopupContainerWithArrow; +import com.android.launcher3.touch.ItemClickHandler; +import com.android.launcher3.touch.ItemLongClickListener; + +/** + * A BubbleTextView with a ring around it's drawable + */ +public class PredictedAppIcon extends BubbleTextView { + + private static final float RING_EFFECT_RATIO = 0.12f; + + private DeviceProfile mDeviceProfile; + private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private boolean mIsPinned = false; + private int mNormalizedIconRadius; + + + public PredictedAppIcon(Context context) { + this(context, null, 0); + } + + public PredictedAppIcon(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PredictedAppIcon(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile(); + mNormalizedIconRadius = IconNormalizer.getNormalizedCircleSize(getIconSize()) / 2; + setOnClickListener(ItemClickHandler.INSTANCE); + setOnFocusChangeListener(Launcher.getLauncher(context).mFocusHandler); + } + + @Override + public void onDraw(Canvas canvas) { + int count = canvas.save(); + if (!mIsPinned) { + drawEffect(canvas); + canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO); + canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO); + } + super.onDraw(canvas); + canvas.restoreToCount(count); + } + + @Override + public void applyFromWorkspaceItem(WorkspaceItemInfo info) { + super.applyFromWorkspaceItem(info); + int color = IconPalette.getMutedColor(info.bitmap.color, 0.54f); + mIconRingPaint.setColor(ColorUtils.setAlphaComponent(color, 200)); + } + + /** + * Removes prediction ring from app icon + */ + public void pin(WorkspaceItemInfo info) { + if (mIsPinned) return; + applyFromWorkspaceItem(info); + setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE); + mIsPinned = true; + invalidate(); + } + + /** + * prepares prediction icon for usage after bind + */ + public void finishBinding() { + setOnLongClickListener((v) -> { + PopupContainerWithArrow.showForIcon((BubbleTextView) v); + if (getParent() != null) { + getParent().requestDisallowInterceptTouchEvent(true); + } + return true; + }); + setTextVisibility(false); + verifyHighRes(); + } + + @Override + public void getIconBounds(Rect outBounds) { + super.getIconBounds(outBounds); + if (!mIsPinned) { + int predictionInset = (int) (getIconSize() * RING_EFFECT_RATIO); + outBounds.inset(predictionInset, predictionInset); + } + } + + private int getOutlineOffsetX() { + return (getMeasuredWidth() / 2) - mNormalizedIconRadius; + } + + private int getOutlineOffsetY() { + return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx; + } + + private void drawEffect(Canvas canvas) { + getShape().drawShape(canvas, getOutlineOffsetX(), getOutlineOffsetY(), + mNormalizedIconRadius, mIconRingPaint); + } + + /** + * Creates and returns a new instance of PredictedAppIcon from WorkspaceItemInfo + */ + public static PredictedAppIcon createIcon(ViewGroup parent, WorkspaceItemInfo info) { + PredictedAppIcon icon = (PredictedAppIcon) LayoutInflater.from(parent.getContext()) + .inflate(R.layout.predicted_app_icon, parent, false); + icon.applyFromWorkspaceItem(info); + return icon; + } + + /** + * Draws Predicted Icon outline on cell layout + */ + public static class PredictedIconOutlineDrawing extends CellLayout.DelegatedCellDrawing { + + private int mOffsetX; + private int mOffsetY; + private int mIconRadius; + private Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + public PredictedIconOutlineDrawing(int cellX, int cellY, PredictedAppIcon icon) { + mDelegateCellX = cellX; + mDelegateCellY = cellY; + mOffsetX = icon.getOutlineOffsetX(); + mOffsetY = icon.getOutlineOffsetY(); + mIconRadius = icon.mNormalizedIconRadius; + mOutlinePaint.setStyle(Paint.Style.STROKE); + mOutlinePaint.setStrokeWidth(5); + mOutlinePaint.setPathEffect(new DashPathEffect(new float[]{15, 15}, 0)); + mOutlinePaint.setColor(Color.argb(100, 245, 245, 245)); + } + + /** + * Draws predicted app icon outline under CellLayout + */ + @Override + public void drawUnderItem(Canvas canvas) { + getShape().drawShape(canvas, mOffsetX, mOffsetY, mIconRadius, mOutlinePaint); + } + + /** + * Draws PredictedAppIcon outline over CellLayout + */ + @Override + public void drawOverItem(Canvas canvas) { + // Does nothing + } + } +} diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 976ccd52ed..89bec98bd2 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -110,7 +110,7 @@ public class CellLayout extends ViewGroup implements Transposable { private OnTouchListener mInterceptTouchListener; - private final ArrayList mFolderBackgrounds = new ArrayList<>(); + private final ArrayList mDelegatedCellDrawings = new ArrayList<>(); final PreviewBackground mFolderLeaveBehind = new PreviewBackground(); private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active }; @@ -219,8 +219,8 @@ public class CellLayout extends ViewGroup implements Transposable { mPreviousReorderDirection[0] = INVALID_DIRECTION; mPreviousReorderDirection[1] = INVALID_DIRECTION; - mFolderLeaveBehind.delegateCellX = -1; - mFolderLeaveBehind.delegateCellY = -1; + mFolderLeaveBehind.mDelegateCellX = -1; + mFolderLeaveBehind.mDelegateCellY = -1; setAlwaysDrawnWithCacheEnabled(false); final Resources res = getResources(); @@ -466,21 +466,18 @@ public class CellLayout extends ViewGroup implements Transposable { } } - for (int i = 0; i < mFolderBackgrounds.size(); i++) { - PreviewBackground bg = mFolderBackgrounds.get(i); - cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation); + for (int i = 0; i < mDelegatedCellDrawings.size(); i++) { + DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i); + cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation); canvas.save(); canvas.translate(mTempLocation[0], mTempLocation[1]); - bg.drawBackground(canvas); - if (!bg.isClipping) { - bg.drawBackgroundStroke(canvas); - } + cellDrawing.drawUnderItem(canvas); canvas.restore(); } - if (mFolderLeaveBehind.delegateCellX >= 0 && mFolderLeaveBehind.delegateCellY >= 0) { - cellToPoint(mFolderLeaveBehind.delegateCellX, - mFolderLeaveBehind.delegateCellY, mTempLocation); + if (mFolderLeaveBehind.mDelegateCellX >= 0 && mFolderLeaveBehind.mDelegateCellY >= 0) { + cellToPoint(mFolderLeaveBehind.mDelegateCellX, + mFolderLeaveBehind.mDelegateCellY, mTempLocation); canvas.save(); canvas.translate(mTempLocation[0], mTempLocation[1]); mFolderLeaveBehind.drawLeaveBehind(canvas); @@ -492,23 +489,28 @@ public class CellLayout extends ViewGroup implements Transposable { protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); - for (int i = 0; i < mFolderBackgrounds.size(); i++) { - PreviewBackground bg = mFolderBackgrounds.get(i); - if (bg.isClipping) { - cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation); - canvas.save(); - canvas.translate(mTempLocation[0], mTempLocation[1]); - bg.drawBackgroundStroke(canvas); - canvas.restore(); - } + for (int i = 0; i < mDelegatedCellDrawings.size(); i++) { + DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i); + cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation); + canvas.save(); + canvas.translate(mTempLocation[0], mTempLocation[1]); + bg.drawOverItem(canvas); + canvas.restore(); } } - public void addFolderBackground(PreviewBackground bg) { - mFolderBackgrounds.add(bg); + /** + * Add Delegated cell drawing + */ + public void addDelegatedCellDrawing(DelegatedCellDrawing bg) { + mDelegatedCellDrawings.add(bg); } - public void removeFolderBackground(PreviewBackground bg) { - mFolderBackgrounds.remove(bg); + + /** + * Remove item from DelegatedCellDrawings + */ + public void removeDelegatedCellDrawing(DelegatedCellDrawing bg) { + mDelegatedCellDrawings.remove(bg); } public void setFolderLeaveBehindCell(int x, int y) { @@ -516,14 +518,14 @@ public class CellLayout extends ViewGroup implements Transposable { mFolderLeaveBehind.setup(getContext(), mActivity, null, child.getMeasuredWidth(), child.getPaddingTop()); - mFolderLeaveBehind.delegateCellX = x; - mFolderLeaveBehind.delegateCellY = y; + mFolderLeaveBehind.mDelegateCellX = x; + mFolderLeaveBehind.mDelegateCellY = y; invalidate(); } public void clearFolderLeaveBehind() { - mFolderLeaveBehind.delegateCellX = -1; - mFolderLeaveBehind.delegateCellY = -1; + mFolderLeaveBehind.mDelegateCellX = -1; + mFolderLeaveBehind.mDelegateCellY = -1; invalidate(); } @@ -2743,6 +2745,24 @@ public class CellLayout extends ViewGroup implements Transposable { } } + /** + * A Delegated cell Drawing for drawing on CellLayout + */ + public abstract static class DelegatedCellDrawing { + public int mDelegateCellX; + public int mDelegateCellY; + + /** + * Draw under CellLayout + */ + public abstract void drawUnderItem(Canvas canvas); + + /** + * Draw over CellLayout + */ + public abstract void drawOverItem(Canvas canvas); + } + /** * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing * if necessary). diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java index b2c0ca7213..2d177d2f2d 100644 --- a/src/com/android/launcher3/folder/PreviewBackground.java +++ b/src/com/android/launcher3/folder/PreviewBackground.java @@ -48,7 +48,7 @@ import com.android.launcher3.views.ActivityContext; * This object represents a FolderIcon preview background. It stores drawing / measurement * information, handles drawing, and animation (accept state <--> rest state). */ -public class PreviewBackground { +public class PreviewBackground extends CellLayout.DelegatedCellDrawing { private static final int CONSUMPTION_ANIMATION_DURATION = 100; @@ -76,8 +76,6 @@ public class PreviewBackground { int basePreviewOffsetY; private CellLayout mDrawingDelegate; - public int delegateCellX; - public int delegateCellY; // When the PreviewBackground is drawn under an icon (for creating a folder) the border // should not occlude the icon @@ -124,6 +122,27 @@ public class PreviewBackground { } }; + /** + * Draws folder background under cell layout + */ + @Override + public void drawUnderItem(Canvas canvas) { + drawBackground(canvas); + if (!isClipping) { + drawBackgroundStroke(canvas); + } + } + + /** + * Draws folder background on cell layout + */ + @Override + public void drawOverItem(Canvas canvas) { + if (isClipping) { + drawBackgroundStroke(canvas); + } + } + public void setup(Context context, ActivityContext activity, View invalidateDelegate, int availableSpaceX, int topPadding) { mInvalidateDelegate = invalidateDelegate; @@ -317,19 +336,19 @@ public class PreviewBackground { private void delegateDrawing(CellLayout delegate, int cellX, int cellY) { if (mDrawingDelegate != delegate) { - delegate.addFolderBackground(this); + delegate.addDelegatedCellDrawing(this); } mDrawingDelegate = delegate; - delegateCellX = cellX; - delegateCellY = cellY; + mDelegateCellX = cellX; + mDelegateCellY = cellY; invalidate(); } private void clearDrawingDelegate() { if (mDrawingDelegate != null) { - mDrawingDelegate.removeFolderBackground(this); + mDrawingDelegate.removeDelegatedCellDrawing(this); } mDrawingDelegate = null; @@ -395,8 +414,8 @@ public class PreviewBackground { // is saved and restored at the beginning of the animation, since cancelling the // existing animation can clear the delgate. CellLayout cl = mDrawingDelegate; - int cellX = delegateCellX; - int cellY = delegateCellY; + int cellX = mDelegateCellX; + int cellY = mDelegateCellY; animateScale(1f, 1f, () -> delegateDrawing(cl, cellX, cellY), this::clearDrawingDelegate); } From b05337167274a9b87569b89fecf0d9f5f83fdf3c Mon Sep 17 00:00:00 2001 From: Zak Cohen Date: Fri, 22 Nov 2019 15:48:33 -0800 Subject: [PATCH 21/24] Make it possible to set a bottom margin on the task thumbnail view. Test: manual test Change-Id: Idb18efeb5e2ee46f789488529513874e028532db --- .../src/com/android/quickstep/views/RecentsView.java | 6 +++++- .../src/com/android/quickstep/views/TaskView.java | 9 +++++++-- quickstep/res/layout/task.xml | 3 ++- quickstep/res/values/dimens.xml | 2 ++ .../src/com/android/quickstep/util/LayoutUtils.java | 3 ++- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index 6e7214e108..b602cea4e7 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -177,6 +177,7 @@ public abstract class RecentsView extends PagedView impl private final float mFastFlingVelocity; private final RecentsModel mModel; private final int mTaskTopMargin; + private final int mTaskBottomMargin; private final ClearAllButton mClearAllButton; private final Rect mClearAllButtonDeadZoneRect = new Rect(); private final Rect mTaskViewDeadZoneRect = new Rect(); @@ -343,6 +344,8 @@ public abstract class RecentsView extends PagedView impl setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); mTaskTopMargin = getResources() .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin); + mTaskBottomMargin = getResources().getDimensionPixelSize( + R.dimen.task_thumbnail_bottom_margin); mSquaredTouchSlop = squaredTouchSlop(context); mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents); @@ -696,6 +699,7 @@ public abstract class RecentsView extends PagedView impl mTaskHeight = mTempRect.height(); mTempRect.top -= mTaskTopMargin; + mTempRect.bottom += mTaskBottomMargin; setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top, dp.widthPx - mInsets.right - mTempRect.right, dp.heightPx - mInsets.bottom - mTempRect.bottom); @@ -1467,7 +1471,7 @@ public abstract class RecentsView extends PagedView impl // Set the pivot points to match the task preview center setPivotY(((mInsets.top + getPaddingTop() + mTaskTopMargin) - + (getHeight() - mInsets.bottom - getPaddingBottom())) / 2); + + (getHeight() - mInsets.bottom - getPaddingBottom() - mTaskBottomMargin)) / 2); setPivotX(((mInsets.left + getPaddingLeft()) + (getWidth() - mInsets.right - getPaddingRight())) / 2); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java index a1775f4284..0bfde64c37 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java @@ -540,8 +540,11 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { } addView(view, indexToAdd); - ((LayoutParams) view.getLayoutParams()).gravity = + LayoutParams layoutParams = (LayoutParams) view.getLayoutParams(); + layoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; + layoutParams.bottomMargin = + ((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin; view.setAlpha(mFooterAlpha); mFooters[index] = new FooterWrapper(view); if (shouldAnimateEntry) { @@ -618,10 +621,12 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private static final class TaskOutlineProvider extends ViewOutlineProvider { private final int mMarginTop; + private final int mMarginBottom; private FullscreenDrawParams mFullscreenParams; TaskOutlineProvider(Resources res, FullscreenDrawParams fullscreenParams) { mMarginTop = res.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin); + mMarginBottom = res.getDimensionPixelSize(R.dimen.task_thumbnail_bottom_margin); mFullscreenParams = fullscreenParams; } @@ -636,7 +641,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { outline.setRoundRect(0, (int) (mMarginTop * scale), (int) ((insets.left + view.getWidth() + insets.right) * scale), - (int) ((insets.top + view.getHeight() + insets.bottom) * scale), + (int) ((insets.top + view.getHeight() + insets.bottom - mMarginBottom) * scale), mFullscreenParams.mCurrentDrawnCornerRadius); } } diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml index 60cfa0cbaa..7a36416c59 100644 --- a/quickstep/res/layout/task.xml +++ b/quickstep/res/layout/task.xml @@ -25,7 +25,8 @@ android:id="@+id/snapshot" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginTop="@dimen/task_thumbnail_top_margin"/> + android:layout_marginTop="@dimen/task_thumbnail_top_margin" + android:layout_marginBottom="@dimen/task_thumbnail_bottom_margin"/> 24dp 12dp + + 0dp 48dp 2dp diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java index 2e118b44df..c47bb4a259 100644 --- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java +++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java @@ -105,6 +105,7 @@ public class LayoutUtils { } float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin); + float bottomMargin = res.getDimension(R.dimen.task_thumbnail_bottom_margin); float paddingVert = res.getDimension(R.dimen.task_card_vert_space); // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless @@ -113,7 +114,7 @@ public class LayoutUtils { int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom; float availableHeight = launcherVisibleHeight - - topIconMargin - extraVerticalSpace - paddingVert; + - topIconMargin - extraVerticalSpace - paddingVert - bottomMargin; float availableWidth = launcherVisibleWidth - paddingHorz; float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight); From f9e73a01a6fe4745e457e14b1d4fe0181824f901 Mon Sep 17 00:00:00 2001 From: Zak Cohen Date: Tue, 3 Dec 2019 12:00:44 -0800 Subject: [PATCH 22/24] Update FeatureFlags documentation. Test: comment change Change-Id: I25f99150f467e4e65d4969ad20a91f18743eceb0 --- src/com/android/launcher3/config/FeatureFlags.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index f4b705e99d..ae30380c78 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -69,16 +69,8 @@ public final class FeatureFlags { * * To add a new flag that can be toggled through the flags UI: * - * 1. Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"), + * Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"), * and set a default value for the flag. This will be the default value on Debug builds. - * - * 2. Add your flag to mTogglableFlags. - * - * 3. Create a getter method (an 'is' method) for the flag by copying an existing one. - * - * 4. Create a getter method with the same name in the release flags copy of FeatureFlags.java. - * This should returns a constant (true/false). This will be the value of the flag used on - * release builds. */ // When enabled the promise icon is visible in all apps while installation an app. public static final TogglableFlag PROMISE_APPS_IN_ALL_APPS = new TogglableFlag( From 2f0b2d3b4e45608d8dc8cc52de3e95ad5333aba5 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Tue, 26 Nov 2019 16:16:02 -0800 Subject: [PATCH 23/24] Dedupe Items on Workspace for hotseat prediction Bug:145236832 Test:Manual Change-Id: I46da38998486c7f58aca829d33b42cbb04da515a --- .../HotseatPredictionController.java | 48 ++++++++++++++----- .../android/launcher3/DeleteDropTarget.java | 1 + 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java index 17a3d9185a..78cf44e1b0 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java @@ -66,6 +66,9 @@ public class HotseatPredictionController implements DragController.DragListener, //TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543) private static final int APPTARGET_ACTION_UNPIN = 4; + private static final String APP_LOCATION_HOTSEAT = "hotseat"; + private static final String APP_LOCATION_WORKSPACE = "workspace"; + private static final String PREDICTION_CLIENT = "hotseat"; private DropTarget.DragObject mDragObject; @@ -204,17 +207,24 @@ public class HotseatPredictionController implements DragController.DragListener, private Bundle getAppPredictionContextExtra() { Bundle bundle = new Bundle(); - ViewGroup vg = mHotseat.getShortcutsAndWidgets(); + bundle.putParcelableArrayList(APP_LOCATION_HOTSEAT, + getPinnedAppTargetsInViewGroup((mHotseat.getShortcutsAndWidgets()))); + bundle.putParcelableArrayList(APP_LOCATION_WORKSPACE, getPinnedAppTargetsInViewGroup( + mLauncher.getWorkspace().getScreenWithId( + Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets())); + return bundle; + } + + private ArrayList getPinnedAppTargetsInViewGroup(ViewGroup viewGroup) { ArrayList pinnedApps = new ArrayList<>(); - for (int i = 0; i < vg.getChildCount(); i++) { - View child = vg.getChildAt(i); + for (int i = 0; i < viewGroup.getChildCount(); i++) { + View child = viewGroup.getChildAt(i); if (isPinnedIcon(child)) { WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) child.getTag(); pinnedApps.add(getAppTargetFromItemInfo(itemInfo)); } } - bundle.putParcelableArrayList("pinned_apps", pinnedApps); - return bundle; + return pinnedApps; } private void setPredictedApps(List appTargets) { @@ -252,7 +262,7 @@ public class HotseatPredictionController implements DragController.DragListener, ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start(); icon.pin(workspaceItemInfo); AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo); - notifyItemAction(appTarget, AppTargetEvent.ACTION_PIN); + notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN); } private List mapToWorkspaceItemInfo( @@ -312,9 +322,10 @@ public class HotseatPredictionController implements DragController.DragListener, } - private void notifyItemAction(AppTarget target, int action) { + private void notifyItemAction(AppTarget target, String location, int action) { if (mAppPredictor != null) { - mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target, action).build()); + mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target, + action).setLaunchLocation(location).build()); } } @@ -336,10 +347,18 @@ public class HotseatPredictionController implements DragController.DragListener, } ItemInfo dragInfo = mDragObject.dragInfo; if (dragInfo instanceof WorkspaceItemInfo && dragInfo.getTargetComponent() != null) { + AppTarget appTarget = getAppTargetFromItemInfo(dragInfo); + if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) { + notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN); + } + if (!isInFirstPage(dragInfo) && isInFirstPage(mDragObject.originalDragInfo)) { + notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN); + } if (isInHotseat(dragInfo) && !isInHotseat(mDragObject.originalDragInfo)) { - notifyItemAction(getAppTargetFromItemInfo(dragInfo), AppTargetEvent.ACTION_PIN); - } else if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) { - notifyItemAction(getAppTargetFromItemInfo(dragInfo), APPTARGET_ACTION_UNPIN); + notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN); + } + if (isInFirstPage(dragInfo) && !isInFirstPage(mDragObject.originalDragInfo)) { + notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN); } } mDragObject = null; @@ -412,7 +431,7 @@ public class HotseatPredictionController implements DragController.DragListener, return false; } ItemInfo info = (ItemInfo) view.getTag(); - return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && ( + return info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION && ( info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT); } @@ -421,6 +440,11 @@ public class HotseatPredictionController implements DragController.DragListener, return itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT; } + private static boolean isInFirstPage(ItemInfo itemInfo) { + return itemInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP + && itemInfo.screenId == Workspace.FIRST_SCREEN_ID; + } + private static AppTarget getAppTargetFromItemInfo(ItemInfo info) { if (info.getTargetComponent() == null) return null; ComponentName cn = info.getTargetComponent(); diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java index 3347b2aef0..bd48aec1ca 100644 --- a/src/com/android/launcher3/DeleteDropTarget.java +++ b/src/com/android/launcher3/DeleteDropTarget.java @@ -113,6 +113,7 @@ public class DeleteDropTarget extends ButtonDropTarget { public void onDrop(DragObject d, DragOptions options) { if (canRemove(d.dragInfo)) { mLauncher.getModelWriter().prepareToUndoDelete(); + d.dragInfo.container = NO_ID; } super.onDrop(d, options); } From 425c60e65cbc7ca361b685c2655cc6476e725ca9 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Tue, 3 Dec 2019 17:33:22 -0800 Subject: [PATCH 24/24] Fix Text shadow issue for predicted icons Bug:142753423 Test:Manual Change-Id: Ib8ce43592421aa0507400eabd9324dd8c6950086 --- .../com/android/launcher3/uioverrides/PredictedAppIcon.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java index e41c75a2c2..f97d6daeda 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java @@ -40,11 +40,12 @@ import com.android.launcher3.icons.IconNormalizer; import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.touch.ItemLongClickListener; +import com.android.launcher3.views.DoubleShadowBubbleTextView; /** * A BubbleTextView with a ring around it's drawable */ -public class PredictedAppIcon extends BubbleTextView { +public class PredictedAppIcon extends DoubleShadowBubbleTextView { private static final float RING_EFFECT_RATIO = 0.12f;