Merge "Fix recents scale sometimes lagging behind window scale" into ub-launcher3-qt-future-dev
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 8db875b..555cc73 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -169,7 +169,7 @@
<activity
android:name="com.android.launcher3.settings.SettingsActivity"
android:label="@string/settings_button_text"
- android:theme="@android:style/Theme.DeviceDefault.Settings"
+ android:theme="@style/HomeSettingsTheme"
android:autoRemoveFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
diff --git a/go/quickstep/res/values/override.xml b/go/quickstep/res/values/override.xml
index 7636fb3..bb267a3 100644
--- a/go/quickstep/res/values/override.xml
+++ b/go/quickstep/res/values/override.xml
@@ -22,5 +22,7 @@
<string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
<string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
+
+ <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
</resources>
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 0000000..fb89013
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -0,0 +1,31 @@
+/*
+ * 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 {
+ }
+
+ public boolean isPeeking() {
+ return false;
+ }
+}
diff --git a/quickstep/recents_ui_overrides/res/values/override.xml b/quickstep/recents_ui_overrides/res/values/override.xml
index 1ddd3f5..ed3ba92 100644
--- a/quickstep/recents_ui_overrides/res/values/override.xml
+++ b/quickstep/recents_ui_overrides/res/values/override.xml
@@ -24,5 +24,7 @@
<string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
<string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
+
+ <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension</string>
</resources>
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 7115943..114fd8e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -17,9 +17,19 @@
package com.android.launcher3;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+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;
@@ -27,10 +37,17 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.view.View;
+import androidx.annotation.NonNull;
+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;
@@ -38,9 +55,6 @@
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
/**
* A {@link QuickstepAppTransitionManagerImpl} that also implements recents transitions from
* {@link RecentsView}.
@@ -50,6 +64,9 @@
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);
@@ -138,14 +155,43 @@
@Override
public int getStateElementAnimationsCount() {
- return 3;
+ return 4;
}
@Override
public Animator createStateElementAnimation(int index, float... values) {
switch (index) {
- case INDEX_SHELF_ANIM:
- return mLauncher.getAllAppsController().createSpringAnimation(values);
+ case INDEX_SHELF_ANIM: {
+ AllAppsTransitionController aatc = mLauncher.getAllAppsController();
+ Animator springAnim = aatc.createSpringAnimation(values);
+
+ if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+ // Translate hotseat with the shelf until reaching overview.
+ float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
+ ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mLauncher);
+ float shiftRange = aatc.getShiftRange();
+ if (values.length == 1) {
+ values = new float[] {aatc.getProgress(), values[0]};
+ }
+ ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
+ hotseatAnim.addUpdateListener(anim -> {
+ float progress = (Float) anim.getAnimatedValue();
+ if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
+ float hotseatShift = (progress - overviewProgress) * shiftRange;
+ mLauncher.getHotseat().setTranslationY(hotseatShift + sat.translationY);
+ }
+ });
+ hotseatAnim.setInterpolator(LINEAR);
+ hotseatAnim.setDuration(springAnim.getDuration());
+
+ AnimatorSet anim = new AnimatorSet();
+ anim.play(hotseatAnim);
+ anim.play(springAnim);
+ return anim;
+ }
+
+ return springAnim;
+ }
case INDEX_RECENTS_FADE_ANIM:
return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
RecentsView.CONTENT_ALPHA, values);
@@ -155,6 +201,20 @@
.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/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index 1a59770..9c66107 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -25,12 +25,16 @@
import android.content.ComponentName;
import android.content.Context;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.AppInfo;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
+import com.android.launcher3.ItemInfo;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.StateListener;
import com.android.launcher3.Utilities;
@@ -39,12 +43,14 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.MainThreadInitializedObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.stream.IntStream;
/**
* Handler responsible to updating the UI due to predicted apps changes. Operations:
@@ -322,6 +328,30 @@
return mCurrentState;
}
+ /**
+ * Fill in predicted_rank field based on app prediction.
+ * Only applicable when {@link ItemInfo#itemType} is one of the followings:
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
+ */
+ public static void fillInPredictedRank(
+ @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
+ final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate();
+ if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null
+ || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+ && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
+ && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
+ return;
+ }
+ final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
+ final List<ComponentKeyMapper> predictedApps = manager.getCurrentState().apps;
+ IntStream.range(0, predictedApps.size())
+ .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
+ .findFirst()
+ .ifPresent((rank) -> target.predictedRank = rank);
+ }
+
public static class PredictionState {
public boolean isEnabled;
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 596bc4f..cac170c 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.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 @@
ArrayList<TouchController> 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/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 468b8af..63ac528 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -98,7 +98,7 @@
if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
// Translate hotseat offscreen if we show it in overview.
ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher);
- scaleAndTranslation.translationY = LayoutUtils.getShelfTrackingDistance(launcher,
+ scaleAndTranslation.translationY += LayoutUtils.getShelfTrackingDistance(launcher,
launcher.getDeviceProfile());
return scaleAndTranslation;
}
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 93d4de1..ed5dba1 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.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;
@@ -32,7 +33,6 @@
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
-import android.content.Context;
import android.graphics.Rect;
import android.view.View;
@@ -47,6 +47,7 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -91,8 +92,19 @@
@Override
public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
- // If the hotseat icons are visible in overview, keep them in their normal position.
- return super.getWorkspaceScaleAndTranslation(launcher);
+ DeviceProfile dp = launcher.getDeviceProfile();
+ if (dp.allAppsIconSizePx >= dp.iconSizePx) {
+ return new ScaleAndTranslation(1, 0, 0);
+ } else {
+ float scale = ((float) dp.allAppsIconSizePx) / dp.iconSizePx;
+ // Distance between the screen center (which is the pivotY for hotseat) and the
+ // bottom of the hotseat (which we want to preserve)
+ float distanceFromBottom = dp.heightPx / 2 - dp.hotseatBarBottomPaddingPx;
+ // On scaling, the bottom edge is moved closer to the pivotY. We move the
+ // hotseat back down so that the bottom edge's position is preserved.
+ float translationY = distanceFromBottom * (1 - scale);
+ return new ScaleAndTranslation(scale, 0, translationY);
+ }
}
return getWorkspaceScaleAndTranslation(launcher);
}
@@ -160,15 +172,7 @@
}
public static float getDefaultSwipeHeight(Launcher launcher) {
- return getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
- }
-
- public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
- float swipeHeight = dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
- if (SysUINavigationMode.getMode(context) == SysUINavigationMode.Mode.NO_BUTTON) {
- swipeHeight -= dp.getInsets().bottom;
- }
- return swipeHeight;
+ return LayoutUtils.getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
}
@Override
@@ -202,6 +206,7 @@
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 e215cfe..3231f37 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,40 +16,37 @@
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;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
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.launcher3.util.VibratorWrapper;
import com.android.quickstep.OverviewInteractionState;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.views.RecentsView;
@@ -78,7 +75,7 @@
@Override
protected long getAtomicDuration() {
- return 300;
+ return LauncherAppTransitionManagerImpl.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
}
@Override
@@ -106,8 +103,7 @@
}
});
mPeekAnim.start();
- recentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1,
peekDuration, 0);
@@ -173,21 +169,14 @@
}
@Override
- public void onDragEnd(float velocity, boolean fling) {
+ public void onDragEnd(float velocity) {
if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) {
if (mPeekAnim != null) {
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) {
@@ -196,7 +185,7 @@
});
overviewAnim.start();
} else {
- super.onDragEnd(velocity, fling);
+ super.onDragEnd(velocity);
}
View searchView = mLauncher.getAppsView().getSearchView();
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index d66af1a..ef50c7b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -43,21 +44,25 @@
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.config.BaseFlags;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.TouchController;
+import com.android.quickstep.util.AssistantUtilities;
import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
/**
* Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps.
*/
-public class NavBarToHomeTouchController implements TouchController, SwipeDetector.Listener {
+public class NavBarToHomeTouchController implements TouchController,
+ SingleAxisSwipeDetector.Listener {
private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3;
private final Launcher mLauncher;
- private final SwipeDetector mSwipeDetector;
+ private final SingleAxisSwipeDetector mSwipeDetector;
private final float mPullbackDistance;
private boolean mNoIntercept;
@@ -67,7 +72,8 @@
public NavBarToHomeTouchController(Launcher launcher) {
mLauncher = launcher;
- mSwipeDetector = new SwipeDetector(mLauncher, this, SwipeDetector.VERTICAL);
+ mSwipeDetector = new SingleAxisSwipeDetector(mLauncher, this,
+ SingleAxisSwipeDetector.VERTICAL);
mPullbackDistance = mLauncher.getResources().getDimension(R.dimen.home_pullback_distance);
}
@@ -79,7 +85,8 @@
if (mNoIntercept) {
return false;
}
- mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+ mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_POSITIVE,
+ false /* ignoreSlop */);
}
if (mNoIntercept) {
@@ -101,6 +108,10 @@
if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
return true;
}
+ if (BaseFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
+ && AssistantUtilities.isExcludedAssistantRunning()) {
+ return true;
+ }
return false;
}
@@ -173,7 +184,8 @@
}
@Override
- public void onDragEnd(float velocity, boolean fling) {
+ public void onDragEnd(float velocity) {
+ boolean fling = mSwipeDetector.isFling(velocity);
final int logAction = fling ? Touch.FLING : Touch.SWIPE;
float progress = mCurrentAnimation.getProgressFraction();
float interpolatedProgress = PULLBACK_INTERPOLATOR.getInterpolation(progress);
@@ -190,6 +202,8 @@
AbstractFloatingView.closeAllOpenViews(mLauncher);
logStateChange(topOpenView.getLogContainerType(), logAction);
}
+ ActivityManagerWrapper.getInstance()
+ .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
} else {
// Quickly return to the state we came from (we didn't move far).
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
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 0000000..7ffffed
--- /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/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 6576ec5..14216ff 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -30,6 +30,7 @@
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.view.MotionEvent;
@@ -42,7 +43,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.quickstep.OverviewInteractionState;
@@ -50,6 +51,7 @@
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
/**
* Handles quick switching to a recent task from the home screen.
@@ -59,10 +61,10 @@
private @Nullable TaskView mTaskToLaunch;
public QuickSwitchTouchController(Launcher launcher) {
- this(launcher, SwipeDetector.HORIZONTAL);
+ this(launcher, SingleAxisSwipeDetector.HORIZONTAL);
}
- protected QuickSwitchTouchController(Launcher l, SwipeDetector.Direction dir) {
+ protected QuickSwitchTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) {
super(l, dir);
}
@@ -94,6 +96,8 @@
super.onDragStart(start);
mStartContainerType = LauncherLogProto.ContainerType.NAVBAR;
mTaskToLaunch = mLauncher.<RecentsView>getOverviewPanel().getTaskViewAt(0);
+ ActivityManagerWrapper.getInstance()
+ .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 00e4f58..ad02de1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -19,6 +19,9 @@
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import android.animation.Animator;
@@ -32,7 +35,8 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.BaseSwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.FlingBlockCheck;
import com.android.launcher3.util.PendingAnimation;
@@ -46,15 +50,14 @@
* Touch controller for handling task view card swipes
*/
public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
- extends AnimatorListenerAdapter implements TouchController, SwipeDetector.Listener {
-
- private static final String TAG = "OverviewSwipeController";
+ extends AnimatorListenerAdapter implements TouchController,
+ SingleAxisSwipeDetector.Listener {
// Progress after which the transition is assumed to be a success in case user does not fling
public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
protected final T mActivity;
- private final SwipeDetector mDetector;
+ private final SingleAxisSwipeDetector mDetector;
private final RecentsView mRecentsView;
private final int[] mTempCords = new int[2];
@@ -74,7 +77,7 @@
public TaskViewTouchController(T activity) {
mActivity = activity;
mRecentsView = activity.getOverviewPanel();
- mDetector = new SwipeDetector(activity, this, SwipeDetector.VERTICAL);
+ mDetector = new SingleAxisSwipeDetector(activity, this, SingleAxisSwipeDetector.VERTICAL);
}
private boolean canInterceptTouch() {
@@ -113,7 +116,7 @@
int directionsToDetectScroll = 0;
boolean ignoreSlopWhenSettling = false;
if (mCurrentAnimation != null) {
- directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+ directionsToDetectScroll = DIRECTION_BOTH;
ignoreSlopWhenSettling = true;
} else {
mTaskBeingDragged = null;
@@ -126,12 +129,12 @@
if (!SysUINavigationMode.getMode(mActivity).hasGestures) {
// Don't allow swipe down to open if we don't support swipe up
// to enter overview.
- directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+ directionsToDetectScroll = DIRECTION_POSITIVE;
} else {
// The task can be dragged up to dismiss it,
// and down to open if it's the current page.
directionsToDetectScroll = i == mRecentsView.getCurrentPage()
- ? SwipeDetector.DIRECTION_BOTH : SwipeDetector.DIRECTION_POSITIVE;
+ ? DIRECTION_BOTH : DIRECTION_POSITIVE;
}
break;
}
@@ -165,8 +168,8 @@
return;
}
int scrollDirections = mDetector.getScrollDirections();
- if (goingUp && ((scrollDirections & SwipeDetector.DIRECTION_POSITIVE) == 0)
- || !goingUp && ((scrollDirections & SwipeDetector.DIRECTION_NEGATIVE) == 0)) {
+ if (goingUp && ((scrollDirections & DIRECTION_POSITIVE) == 0)
+ || !goingUp && ((scrollDirections & DIRECTION_NEGATIVE) == 0)) {
// Trying to re-init in an unsupported direction.
return;
}
@@ -243,7 +246,8 @@
}
@Override
- public void onDragEnd(float velocity, boolean fling) {
+ public void onDragEnd(float velocity) {
+ boolean fling = mDetector.isFling(velocity);
final boolean goingToEnd;
final int logAction;
boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
@@ -260,7 +264,7 @@
logAction = Touch.SWIPE;
goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS;
}
- long animationDuration = SwipeDetector.calculateDuration(
+ long animationDuration = BaseSwipeDetector.calculateDuration(
velocity, goingToEnd ? (1 - progress) : progress);
if (blockedFling && !goingToEnd) {
animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
index f1e4041..0ed5291 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
@@ -17,12 +17,12 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
public class TransposedQuickSwitchTouchController extends QuickSwitchTouchController {
public TransposedQuickSwitchTouchController(Launcher launcher) {
- super(launcher, SwipeDetector.VERTICAL);
+ super(launcher, SingleAxisSwipeDetector.VERTICAL);
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index 7196f7c..5cce53e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -15,15 +15,12 @@
*/
package com.android.quickstep;
-import static android.os.VibrationEffect.EFFECT_CLICK;
-import static android.os.VibrationEffect.createPredefined;
-
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
@@ -39,9 +36,6 @@
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.provider.Settings;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
@@ -56,6 +50,7 @@
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
@@ -105,7 +100,6 @@
protected final ClipAnimationHelper mClipAnimationHelper;
protected final TransformParams mTransformParams = new TransformParams();
- private final Vibrator mVibrator;
protected final Mode mMode;
// Shift in the range of [0, 1].
@@ -146,7 +140,6 @@
mClipAnimationHelper = new ClipAnimationHelper(context);
mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
- mVibrator = context.getSystemService(Vibrator.class);
initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
.getDeviceProfile(mContext));
}
@@ -160,19 +153,7 @@
}
protected void performHapticFeedback() {
- if (!mVibrator.hasVibrator()) {
- return;
- }
- if (Settings.System.getInt(
- mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 0) {
- return;
- }
-
- VibrationEffect effect = createPredefined(EFFECT_CLICK);
- if (effect == null) {
- return;
- }
- UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(effect));
+ VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
}
public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) {
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 57e6d96..eefde12 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.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,15 +52,15 @@
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;
@@ -167,18 +166,8 @@
@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();
}
};
}
@@ -201,7 +190,9 @@
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 +221,7 @@
@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/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 2161591..3c78dd8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -7,7 +7,6 @@
import com.android.launcher3.testing.TestInformationHandler;
import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
@@ -25,7 +24,7 @@
switch (method) {
case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
final float swipeHeight =
- OverviewState.getDefaultSwipeHeight(mContext, mDeviceProfile);
+ LayoutUtils.getDefaultSwipeHeight(mContext, mDeviceProfile);
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
return response;
}
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 729287c..f321826 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -42,7 +42,6 @@
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.Service;
-import android.app.TaskInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -74,6 +73,7 @@
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.BaseFlags;
import com.android.launcher3.logging.EventLogArray;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.model.AppLaunchTracker;
@@ -92,6 +92,7 @@
import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer;
import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
+import com.android.quickstep.util.AssistantUtilities;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -102,7 +103,6 @@
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.shared.system.RecentsAnimationListener;
import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
-import com.android.systemui.shared.system.TaskInfoCompat;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -649,16 +649,14 @@
mOverviewComponentObserver.getActivityControlHelper();
boolean forceOverviewInputConsumer = false;
- if (isExcludedAssistant(runningTaskInfo)) {
+ if (AssistantUtilities.isExcludedAssistant(runningTaskInfo)) {
// In the case where we are in the excluded assistant state, ignore it and treat the
// running activity as the task behind the assistant
- runningTaskInfo = mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT);
- if (!ActivityManagerWrapper.isHomeTask(runningTaskInfo)) {
- final ComponentName homeComponent =
- mOverviewComponentObserver.getHomeIntent().getComponent();
- forceOverviewInputConsumer =
- runningTaskInfo.baseIntent.getComponent(). equals(homeComponent);
- }
+ runningTaskInfo = mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT /* ignoreActivityType */);
+ ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent();
+ ComponentName runningComponent = runningTaskInfo.baseIntent.getComponent();
+ forceOverviewInputConsumer =
+ runningComponent != null && runningComponent.equals(homeComponent);
}
if (runningTaskInfo == null && !sSwipeSharedState.goingToLauncher
@@ -672,9 +670,9 @@
return createOtherActivityInputConsumer(event, info);
} else if (sSwipeSharedState.goingToLauncher || activityControl.isResumed()
|| forceOverviewInputConsumer) {
- return createOverviewInputConsumer(event);
+ return createOverviewInputConsumer(event, forceOverviewInputConsumer);
} else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityControl.isInLiveTileMode()) {
- return createOverviewInputConsumer(event);
+ return createOverviewInputConsumer(event, forceOverviewInputConsumer);
} else if (mGestureBlockingActivity != null && runningTaskInfo != null
&& mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
return mResetGestureInputConsumer;
@@ -683,12 +681,6 @@
}
}
- private boolean isExcludedAssistant(TaskInfo info) {
- return info != null
- && TaskInfoCompat.getActivityType(info) == ACTIVITY_TYPE_ASSISTANT
- && (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
- }
-
private boolean disableHorizontalSwipe(MotionEvent event) {
// mExclusionRegion can change on binder thread, use a local instance here.
Region exclusionRegion = mExclusionRegion;
@@ -726,7 +718,8 @@
}
}
- public InputConsumer createOverviewInputConsumer(MotionEvent event) {
+ public InputConsumer createOverviewInputConsumer(MotionEvent event,
+ boolean forceOverviewInputConsumer) {
final ActivityControlHelper activityControl =
mOverviewComponentObserver.getActivityControlHelper();
BaseDraggingActivity activity = activityControl.getCreatedActivity();
@@ -734,7 +727,10 @@
return mResetGestureInputConsumer;
}
- if (activity.getRootView().hasWindowFocus() || sSwipeSharedState.goingToLauncher) {
+ if (activity.getRootView().hasWindowFocus()
+ || sSwipeSharedState.goingToLauncher
+ || (BaseFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
+ && forceOverviewInputConsumer)) {
return new OverviewInputConsumer(activity, mInputMonitorCompat,
false /* startingInActivityBounds */);
} else {
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 ff134b1..2fa4feb 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.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.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 @@
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;
/**
@@ -440,7 +440,7 @@
@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/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 05cbb78..f40d552 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -22,9 +22,9 @@
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.content.Context;
+import android.content.Intent;
import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -34,12 +34,9 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.StatsLogUtils;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.quickstep.OverviewCallbacks;
import com.android.quickstep.util.NavBarPosition;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputMonitorCompat;
public class OverviewWithoutFocusInputConsumer implements InputConsumer {
@@ -148,9 +145,9 @@
}
if (triggerQuickstep) {
- OverviewCallbacks.get(mContext).closeAllWindows();
- ActivityManagerWrapper.getInstance()
- .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ mContext.startActivity(new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
TOUCH_INTERACTION_LOG.addLog("startQuickstep");
BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
int pageIndex = -1; // This number doesn't reflect workspace page index.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
new file mode 100644
index 0000000..b251f9e
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 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.logging;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.appprediction.PredictionUiStateManager;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+/**
+ * This class handles AOSP MetricsLogger function calls and logging around
+ * quickstep interactions and app launches.
+ */
+@SuppressWarnings("unused")
+public class UserEventDispatcherAppPredictionExtension extends UserEventDispatcherExtension {
+
+ public static final int ALL_APPS_PREDICTION_TIPS = 2;
+
+ private static final String TAG = "UserEventDispatcher";
+
+ public UserEventDispatcherAppPredictionExtension(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onFillInLogContainerData(
+ @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target,
+ @NonNull LauncherLogProto.Target targetParent) {
+ PredictionUiStateManager.fillInPredictedRank(itemInfo, target);
+ }
+}
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 0000000..83bc416
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -0,0 +1,105 @@
+/*
+ * 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/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 1aa5365..5f02de2 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 @@
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 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.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 @@
* @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 @@
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();
@@ -161,6 +166,21 @@
}
/**
+ * 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.<RecentsView>getOverviewPanel().getScroller().forceFinished(true);
+ }
+
+ /**
* Starts the animation.
*/
public void start() {
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 5c4d6d8..292eaaa 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -21,8 +21,6 @@
<!-- Activity which blocks home gesture -->
<string name="gesture_blocking_activity" translatable="false"></string>
- <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
-
<string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
<string name="test_information_handler_class" translatable="false">com.android.quickstep.QuickstepTestInformationHandler</string>
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 991408c..a91410c 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -57,6 +57,9 @@
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.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.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 @@
private RemoteAnimationProvider mRemoteAnimationProvider;
+ private final ShelfPeekAnim mShelfPeekAnim;
+
private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -177,6 +180,12 @@
mLauncher.addOnDeviceProfileChangeListener(this);
registerRemoteAnimations();
+
+ mShelfPeekAnim = new ShelfPeekAnim(mLauncher);
+ }
+
+ public ShelfPeekAnim getShelfPeekAnim() {
+ return mShelfPeekAnim;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
index bb72315..39b0f8d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
@@ -11,7 +11,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationComponents;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.quickstep.RecentsModel;
@@ -24,7 +24,7 @@
private static final String TAG = "LandscapeEdgeSwipeCtrl";
public LandscapeEdgeSwipeController(Launcher l) {
- super(l, SwipeDetector.HORIZONTAL);
+ super(l, SingleAxisSwipeDetector.HORIZONTAL);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 9813295..db6a40f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -43,7 +43,7 @@
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -79,7 +79,7 @@
private boolean mFinishFastOnSecondTouch;
public PortraitStatesTouchController(Launcher l, boolean allowDragToOverview) {
- super(l, SwipeDetector.VERTICAL);
+ super(l, SingleAxisSwipeDetector.VERTICAL);
mOverviewPortraitStateTouchHelper = new PortraitOverviewStateTouchHelper(l);
mAllowDragToOverview = allowDragToOverview;
}
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 5c9c7d4..110cc23 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.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 @@
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 @@
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
diff --git a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
index 4a11601..9ca7f23 100644
--- a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
+++ b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
@@ -20,10 +20,10 @@
import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CANCEL_TARGET;
-import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
import static com.android.systemui.shared.system.LauncherEventUtil.DISMISS;
import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_QUICK_SCRUB_ONBOARDING_TIP;
import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_SWIPE_UP_ONBOARDING_TIP;
+import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
diff --git a/quickstep/src/com/android/quickstep/util/AssistantUtilities.java b/quickstep/src/com/android/quickstep/util/AssistantUtilities.java
new file mode 100644
index 0000000..552db1f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/AssistantUtilities.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.quickstep.util;
+
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT;
+
+import android.annotation.TargetApi;
+import android.app.TaskInfo;
+import android.content.Intent;
+import android.os.Build;
+
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskInfoCompat;
+
+/**
+ * Utility class for interacting with the Assistant.
+ */
+@TargetApi(Build.VERSION_CODES.Q)
+public final class AssistantUtilities {
+
+ /** Returns true if an Assistant activity that is excluded from recents is running. */
+ public static boolean isExcludedAssistantRunning() {
+ return isExcludedAssistant(ActivityManagerWrapper.getInstance().getRunningTask());
+ }
+
+ /** Returns true if the given task holds an Assistant activity that is excluded from recents. */
+ public static boolean isExcludedAssistant(TaskInfo info) {
+ return info != null
+ && TaskInfoCompat.getActivityType(info) == ACTIVITY_TYPE_ASSISTANT
+ && (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+ }
+
+ private AssistantUtilities() {}
+}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 050bdff..2e118b4 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -26,7 +26,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.SysUINavigationMode;
import java.lang.annotation.Retention;
@@ -39,12 +39,27 @@
@IntDef({MULTI_WINDOW_STRATEGY_HALF_SCREEN, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE})
private @interface MultiWindowStrategy {}
+ /**
+ * The height for the swipe up motion
+ */
+ public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
+ float swipeHeight = dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
+ if (SysUINavigationMode.getMode(context) == SysUINavigationMode.Mode.NO_BUTTON) {
+ swipeHeight -= dp.getInsets().bottom;
+ }
+ return swipeHeight;
+ }
+
public static void calculateLauncherTaskSize(Context context, DeviceProfile dp, Rect outRect) {
float extraSpace;
if (dp.isVerticalBarLayout()) {
extraSpace = 0;
} else {
- extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx;
+ Resources res = context.getResources();
+
+ extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx
+ + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
+ + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
}
calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect);
}
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 26e9eaf..3320dae 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -18,6 +18,7 @@
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,15 +36,18 @@
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;
-import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ScrimView;
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
@@ -163,7 +167,7 @@
int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom
+ hotseatPadding.bottom + hotseatPadding.top;
float dragHandleTop =
- Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(context, dp));
+ Math.min(hotseatSize, LayoutUtils.getDefaultSwipeHeight(context, dp));
mDragHandleProgress = 1 - (dragHandleTop / mShiftRange);
}
mTopOffset = dp.getInsets().top - mShelfOffset;
@@ -193,8 +197,12 @@
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/res/values/styles.xml b/res/values/styles.xml
index 339aef5..80c791c 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -130,6 +130,10 @@
<item name="widgetsTheme">@style/WidgetContainerTheme</item>
</style>
+ <style name="HomeSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings">
+ <item name="android:navigationBarColor">@android:color/transparent</item>
+ </style>
+
<!--
Theme overrides to element on homescreen, i.e., which are drawn on top on wallpaper.
Various foreground colors are overridden to be workspaceTextColor so that they are properly
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index a35f598..bc6fa6e 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -403,14 +403,15 @@
Point totalWorkspacePadding = getTotalWorkspacePadding();
// Check if the icons fit within the available height.
- float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize;
- int maxHeight = availableHeightPx - totalWorkspacePadding.y - folderMargin;
- float scaleY = maxHeight / usedHeight;
+ float contentUsedHeight = folderCellHeightPx * inv.numFolderRows;
+ int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize
+ - folderMargin;
+ float scaleY = contentMaxHeight / contentUsedHeight;
// Check if the icons fit within the available width.
- float usedWidth = folderCellWidthPx * inv.numFolderColumns;
- int maxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
- float scaleX = maxWidth / usedWidth;
+ float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns;
+ int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
+ float scaleX = contentMaxWidth / contentUsedWidth;
float scale = Math.min(scaleX, scaleY);
if (scale < 1f) {
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 17fbb79..fe91602 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -21,6 +21,7 @@
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -38,6 +39,7 @@
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.compat.LauncherAppsCompat;
@@ -133,7 +135,8 @@
String pkg = getIntentPackage(info.launchIntent);
if (!TextUtils.isEmpty(pkg)
- && !launcherApps.isPackageEnabledForProfile(pkg, info.user)) {
+ && !launcherApps.isPackageEnabledForProfile(pkg, info.user)
+ && !info.isActivity) {
if (DBG) {
Log.d(TAG, "Ignoring shortcut for absent package: " + info.launchIntent);
}
@@ -238,11 +241,6 @@
return info == null ? null : (WorkspaceItemInfo) info.getItemInfo().first;
}
- public static WorkspaceItemInfo fromActivityInfo(LauncherActivityInfo info, Context context) {
- return (WorkspaceItemInfo)
- new PendingInstallShortcutInfo(info, context).getItemInfo().first;
- }
-
public static void queueShortcut(ShortcutInfo info, Context context) {
queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
}
@@ -318,10 +316,10 @@
private static class PendingInstallShortcutInfo {
final boolean isActivity;
- final ShortcutInfo shortcutInfo;
- final AppWidgetProviderInfo providerInfo;
+ @Nullable final ShortcutInfo shortcutInfo;
+ @Nullable final AppWidgetProviderInfo providerInfo;
- final Intent data;
+ @Nullable final Intent data;
final Context mContext;
final Intent launchIntent;
final String label;
@@ -351,7 +349,12 @@
shortcutInfo = null;
providerInfo = null;
- data = null;
+ String packageName = info.getComponentName().getPackageName();
+ data = new Intent();
+ data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent().setComponent(
+ new ComponentName(packageName, "")).setPackage(packageName));
+ data.putExtra(Intent.EXTRA_SHORTCUT_NAME, info.getLabel());
+
user = info.getUser();
mContext = context;
@@ -445,21 +448,26 @@
// This name is only used for comparisons and notifications, so fall back to activity
// name if not supplied
String name = ensureValidName(mContext, launchIntent, label).toString();
- Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
- Intent.ShortcutIconResource iconResource =
- data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+ Bitmap icon = data == null ? null
+ : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+ Intent.ShortcutIconResource iconResource = data == null ? null
+ : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
// Only encode the parameters which are supported by the API.
JSONStringer json = new JSONStringer()
.object()
.key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
.key(NAME_KEY).value(name)
+ .key(USER_HANDLE_KEY).value(
+ UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user))
.key(APP_SHORTCUT_TYPE_KEY).value(isActivity);
if (icon != null) {
byte[] iconByteArray = GraphicsUtils.flattenBitmap(icon);
- json = json.key(ICON_KEY).value(
- Base64.encodeToString(
- iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
+ if (iconByteArray != null) {
+ json = json.key(ICON_KEY).value(
+ Base64.encodeToString(
+ iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
+ }
}
if (iconResource != null) {
json = json.key(ICON_RESOURCE_NAME_KEY).value(iconResource.resourceName);
@@ -475,7 +483,7 @@
public Pair<ItemInfo, Object> getItemInfo() {
if (isActivity) {
- WorkspaceItemInfo si = createWorkspaceItemInfo(data,
+ WorkspaceItemInfo si = createWorkspaceItemInfo(data, user,
LauncherAppState.getInstance(mContext));
si.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
@@ -500,7 +508,7 @@
return Pair.create(widgetInfo, providerInfo);
} else {
WorkspaceItemInfo itemInfo =
- createWorkspaceItemInfo(data, LauncherAppState.getInstance(mContext));
+ createWorkspaceItemInfo(data, user, LauncherAppState.getInstance(mContext));
return Pair.create(itemInfo, null);
}
}
@@ -618,7 +626,8 @@
return new PendingInstallShortcutInfo(info, original.mContext);
}
- private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, LauncherAppState app) {
+ private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, UserHandle user,
+ LauncherAppState app) {
if (data == null) {
Log.e(TAG, "Can't construct WorkspaceItemInfo with null data");
return null;
@@ -635,10 +644,7 @@
}
final WorkspaceItemInfo info = new WorkspaceItemInfo();
-
- // Only support intents for current user for now. Intents sent from other
- // users wouldn't get here without intent forwarding anyway.
- info.user = Process.myUserHandle();
+ info.user = user;
BitmapInfo iconInfo = null;
LauncherIcons li = LauncherIcons.obtain(app.getContext());
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 9bf690f..8086045 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -97,6 +97,7 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.folder.FolderGridOrganizer;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.FolderNameProvider;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.keyboard.CustomActionsPopup;
@@ -557,6 +558,10 @@
return mStateManager;
}
+ public FolderNameProvider getFolderNameProvider() {
+ return new FolderNameProvider();
+ }
+
@Override
public <T extends View> T findViewById(int id) {
return mLauncherView.findViewById(id);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index c0cf135..a012412 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -134,11 +134,11 @@
/**
* Updates the icons and label of all pending icons for the provided package name.
*/
- public void updateSessionDisplayInfo(final String packageName) {
+ public void updateSessionDisplayInfo(final String packageName, final UserHandle user) {
HashSet<String> packages = new HashSet<>();
packages.add(packageName);
enqueueModelUpdateTask(new CacheDataUpdatedTask(
- CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages));
+ CacheDataUpdatedTask.OP_SESSION_UPDATE, user, packages));
}
/**
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 848e19f..f673508 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -24,7 +24,8 @@
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.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.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/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index 6853bf6..a87c446 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -71,8 +71,13 @@
SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
- PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context);
+ if (!PackageInstaller.ACTION_SESSION_COMMITTED.equals(intent.getAction())
+ || info == null || user == null) {
+ // Invalid intent.
+ return;
+ }
+ PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context);
if (TextUtils.isEmpty(info.getAppPackageName())
|| info.getInstallReason() != PackageManager.INSTALL_REASON_USER
|| packageInstallerCompat.promiseIconAddedForId(info.getSessionId())) {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index c5f26aa..5d0effa 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -584,7 +584,7 @@
LauncherIcons li = LauncherIcons.obtain(appState.getContext());
Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap;
li.recycle();
- float badgeSize = iconSize * LauncherIcons.getBadgeSizeForIconSize(iconSize);
+ float badgeSize = LauncherIcons.getBadgeSizeForIconSize(iconSize);
float insetFraction = (iconSize - badgeSize) / iconSize;
return new InsetDrawable(new FastBitmapDrawable(badge),
insetFraction, insetFraction, 0, 0);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 474c59d..9eeb286 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1275,6 +1275,10 @@
return !mLauncher.isInState(NORMAL);
}
+ private boolean workspaceInScrollableState() {
+ return mLauncher.isInState(SPRING_LOADED) || !workspaceInModalState();
+ }
+
/** Returns whether a drag should be allowed to be started from the current workspace state. */
public boolean workspaceIconsCanBeDragged() {
return mLauncher.getStateManager().getState().workspaceIconsCanBeDragged;
@@ -2879,7 +2883,7 @@
@Override
public boolean scrollLeft() {
boolean result = false;
- if (!workspaceInModalState() && !mIsSwitchingState) {
+ if (!mIsSwitchingState && workspaceInScrollableState()) {
result = super.scrollLeft();
}
Folder openFolder = Folder.getOpen(mLauncher);
@@ -2892,7 +2896,7 @@
@Override
public boolean scrollRight() {
boolean result = false;
- if (!workspaceInModalState() && !mIsSwitchingState) {
+ if (!mIsSwitchingState && workspaceInScrollableState()) {
result = super.scrollRight();
}
Folder openFolder = Folder.getOpen(mLauncher);
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 40c6b5f..7a7e1fe 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -38,7 +38,6 @@
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
/**
@@ -96,14 +95,13 @@
propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
if (!hotseat.getRotationMode().isTransposed) {
- // Set the hotseat's pivot point to match the workspace's, so that it scales together.
- DragLayer dragLayer = mLauncher.getDragLayer();
- float[] workspacePivot =
- new float[]{ mWorkspace.getPivotX(), mWorkspace.getPivotY() };
- dragLayer.getDescendantCoordRelativeToSelf(mWorkspace, workspacePivot);
- dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot);
- hotseat.setPivotX(workspacePivot[0]);
- hotseat.setPivotY(workspacePivot[1]);
+ // Set the hotseat's pivot point to match the workspace's, so that it scales
+ // together. Since both hotseat and workspace can move, transform the point
+ // manually instead of using dragLayer.getDescendantCoordRelativeToSelf and
+ // related methods.
+ hotseat.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop() - hotseat.getTop());
+ hotseat.setPivotX(mWorkspace.getPivotX()
+ + mWorkspace.getLeft() - hotseat.getLeft());
}
float hotseatScale = hotseatScaleAndTranslation.scale;
Interpolator hotseatScaleInterpolator = builder.getInterpolator(ANIM_HOTSEAT_SCALE,
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 3836c9f..08ce9c2 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -2,8 +2,6 @@
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
@@ -134,15 +132,6 @@
} else {
mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
}
-
- if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
- // Translate hotseat with the shelf until reaching overview.
- float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
- if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
- float hotseatShift = (progress - overviewProgress) * mShiftRange;
- mLauncher.getHotseat().setTranslationY(hotseatShift);
- }
- }
}
public float getProgress() {
diff --git a/src/com/android/launcher3/anim/AlphaUpdateListener.java b/src/com/android/launcher3/anim/AlphaUpdateListener.java
index 8ac9d66..eabd283 100644
--- a/src/com/android/launcher3/anim/AlphaUpdateListener.java
+++ b/src/com/android/launcher3/anim/AlphaUpdateListener.java
@@ -27,7 +27,7 @@
*/
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 2c440bb..4a52795 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -26,15 +26,15 @@
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 @@
}
}
+ /**
+ * 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 @@
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 c45cd85..fccc120 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -39,6 +39,7 @@
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 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/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index cdb5c4d..39f6949 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -22,6 +22,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionCallback;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Bundle;
@@ -30,6 +31,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.PackageUserKey;
import java.util.List;
@@ -90,4 +92,8 @@
@Nullable PackageUserKey packageUser);
public abstract List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions();
+
+ public abstract void registerSessionCallback(LooperExecutor executor,
+ SessionCallback sessionCallback);
+ public abstract void unregisterSessionCallback(SessionCallback sessionCallback);
}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
index 1885d8f..281274c 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
@@ -23,6 +23,7 @@
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionCallback;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
@@ -35,6 +36,7 @@
import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVL;
import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
@@ -212,5 +214,17 @@
public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
return mContext.getPackageManager().getPackageInstaller().getAllSessions();
}
+
+ @Override
+ public void registerSessionCallback(LooperExecutor executor, SessionCallback sessionCallback) {
+ mContext.getPackageManager().getPackageInstaller().registerSessionCallback(sessionCallback,
+ executor.getHandler());
+ }
+
+ @Override
+ public void unregisterSessionCallback(SessionCallback sessionCallback) {
+ mContext.getPackageManager().getPackageInstaller()
+ .unregisterSessionCallback(sessionCallback);
+ }
}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java b/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java
index 0a1811e..48805af 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java
@@ -18,8 +18,10 @@
import android.annotation.TargetApi;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionCallback;
+
+import com.android.launcher3.util.LooperExecutor;
import java.util.List;
@@ -33,4 +35,14 @@
public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
return mLauncherApps.getAllPackageInstallerSessions();
}
+
+ @Override
+ public void registerSessionCallback(LooperExecutor executor, SessionCallback sessionCallback) {
+ mLauncherApps.registerPackageInstallerSessionCallback(executor, sessionCallback);
+ }
+
+ @Override
+ public void unregisterSessionCallback(SessionCallback sessionCallback) {
+ mLauncherApps.unregisterPackageInstallerSessionCallback(sessionCallback);
+ }
}
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
index 9c57e8a..409b21d 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
@@ -28,7 +28,6 @@
import android.text.TextUtils;
import android.util.SparseArray;
-import com.android.launcher3.LauncherModel;
import com.android.launcher3.SessionCommitReceiver;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.IconCache;
@@ -63,8 +62,8 @@
mAppContext = context.getApplicationContext();
mInstaller = context.getPackageManager().getPackageInstaller();
mCache = LauncherAppState.getInstance(context).getIconCache();
- mInstaller.registerSessionCallback(mCallback, MODEL_EXECUTOR.getHandler());
mLauncherApps = LauncherAppsCompat.getInstance(context);
+ mLauncherApps.registerSessionCallback(MODEL_EXECUTOR, mCallback);
mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString(
getPrefs(context).getString(PROMISE_ICON_IDS, "")));
@@ -126,7 +125,7 @@
@Override
public void onStop() {
- mInstaller.unregisterSessionCallback(mCallback);
+ mLauncherApps.unregisterSessionCallback(mCallback);
}
@Thunk void sendUpdate(PackageInstallInfo info) {
@@ -222,12 +221,14 @@
private SessionInfo pushSessionDisplayToLauncher(int sessionId) {
SessionInfo session = verify(mInstaller.getSessionInfo(sessionId));
if (session != null && session.getAppPackageName() != null) {
+ UserHandle user = getUserHandle(session);
mActiveSessions.put(session.getSessionId(),
- new PackageUserKey(session.getAppPackageName(), getUserHandle(session)));
- addSessionInfoToCache(session, getUserHandle(session));
+ new PackageUserKey(session.getAppPackageName(), user));
+ addSessionInfoToCache(session, user);
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app != null) {
- app.getModel().updateSessionDisplayInfo(session.getAppPackageName());
+ app.getModel().updateSessionDisplayInfo(session.getAppPackageName(),
+ user);
}
return session;
}
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 7c8fe16..64d236f 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -108,6 +108,10 @@
"FAKE_LANDSCAPE_UI", false,
"Rotate launcher UI instead of using transposed layout");
+ public static final TogglableFlag FOLDER_NAME_SUGGEST = new TogglableFlag(
+ "FOLDER_NAME_SUGGEST", true,
+ "Suggests folder names instead of blank text.");
+
public static final TogglableFlag APP_SEARCH_IMPROVEMENTS = new TogglableFlag(
"APP_SEARCH_IMPROVEMENTS", false,
"Adds localized title and keyword search and ranking");
@@ -115,6 +119,9 @@
public static final TogglableFlag ENABLE_PREDICTION_DISMISS = new TogglableFlag(
"ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list");
+ public static final TogglableFlag ASSISTANT_GIVES_LAUNCHER_FOCUS = new TogglableFlag(
+ "ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
+ "Allow Launcher to handle nav bar gestures while Assistant is running over it");
public static void initialize(Context context) {
// Avoid the disk read for user builds
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 65d593c..0bd2c9a 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -74,7 +74,10 @@
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logging.LoggerUtils;
import com.android.launcher3.pageindicators.PageIndicatorDots;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ClipPathView;
@@ -145,7 +148,7 @@
public ExtendedEditText mFolderName;
private PageIndicatorDots mPageIndicator;
- private View mFooter;
+ protected View mFooter;
private int mFooterHeight;
// Cell ranks used for drag and drop
@@ -401,7 +404,6 @@
mFolderName.setText("");
mFolderName.setHint(R.string.folder_hint_text);
}
-
// In case any children didn't come across during loading, clean up the folder accordingly
mFolderIcon.post(() -> {
if (getItemCount() <= 1) {
@@ -410,6 +412,22 @@
});
}
+
+ /**
+ * Show suggested folder title.
+ */
+ public void showSuggestedTitle(CharSequence suggestName) {
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && mInfo.contents.size() == 2) {
+ if (!TextUtils.isEmpty(suggestName)) {
+ mFolderName.setHint(suggestName);
+ mFolderName.setText(suggestName);
+ mFolderName.showKeyboard();
+ mInfo.title = suggestName;
+ }
+ animateOpen();
+ }
+ }
+
/**
* Creates a new UserFolder, inflated from R.layout.user_folder.
*
@@ -532,8 +550,6 @@
// dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
mDeleteFolderOnDropCompleted = false;
- centerAboutIcon();
-
AnimatorSet anim = new FolderAnimationManager(this, true /* isOpening */).getAnimator();
anim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -546,7 +562,11 @@
mState = STATE_OPEN;
announceAccessibilityChanges();
- mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("folder opened");
+ mLauncher.getUserEventDispatcher().logActionOnItem(
+ Touch.TAP,
+ Direction.NONE,
+ ItemType.FOLDER_ICON, mInfo.cellX, mInfo.cellY);
+
mContent.setFocusOnFirstChild();
}
});
@@ -592,7 +612,6 @@
if (mDragController.isDragging()) {
mDragController.forceTouchMove();
}
-
mContent.verifyVisibleHighResIcons(mContent.getNextPage());
}
@@ -877,7 +896,6 @@
// Reordering may have occured, and we need to save the new item locations. We do this once
// at the end to prevent unnecessary database operations.
updateItemLocationsInDatabaseBatch();
-
// Use the item count to check for multi-page as the folder UI may not have
// been refreshed yet.
if (getItemCount() <= mContent.itemsPerPage()) {
@@ -973,10 +991,10 @@
lp.y = top;
}
- private int getContentAreaHeight() {
+ protected int getContentAreaHeight() {
DeviceProfile grid = mLauncher.getDeviceProfile();
- int maxContentAreaHeight = grid.availableHeightPx
- - grid.getTotalWorkspacePadding().y - mFooterHeight;
+ int maxContentAreaHeight = grid.availableHeightPx - grid.getTotalWorkspacePadding().y
+ - mFooterHeight;
int height = Math.min(maxContentAreaHeight,
mContent.getDesiredHeight());
return Math.max(height, MIN_CONTENT_DIMEN);
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 9eb0693..1310d37 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -59,6 +59,8 @@
*/
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 @@
* 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 @@
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 @@
// 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 @@
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 @@
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);
+
}
});
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 3840639..fd6d1e3 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -58,12 +58,14 @@
import com.android.launcher3.Workspace;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.dragndrop.BaseItemDragListener;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.IconLabelDotView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -368,12 +370,17 @@
if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
final int finalIndex = index;
- postDelayed(new Runnable() {
- public void run() {
- mPreviewItemManager.hidePreviewItem(finalIndex, false);
- mFolder.showItem(item);
- invalidate();
- }
+
+ String[] suggestedNameOut = new String[1];
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+ Executors.UI_HELPER_EXECUTOR.post(() -> mLauncher.getFolderNameProvider()
+ .getSuggestedFolderName(getContext(), mInfo.contents, suggestedNameOut));
+ }
+ postDelayed(() -> {
+ mPreviewItemManager.hidePreviewItem(finalIndex, false);
+ mFolder.showItem(item);
+ invalidate();
+ mFolder.showSuggestedTitle(suggestedNameOut[0]);
}, DROP_IN_ANIMATION_DURATION);
} else {
addItem(item);
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
new file mode 100644
index 0000000..0a1221e
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -0,0 +1,61 @@
+/*
+ * 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.folder;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.WorkspaceItemInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Locates provider for the folder name.
+ */
+public class FolderNameProvider {
+
+ /**
+ * Returns suggested folder name.
+ */
+ public CharSequence getSuggestedFolderName(Context context,
+ ArrayList<WorkspaceItemInfo> workspaceItemInfos, CharSequence[] suggestName) {
+ // Currently only run the algorithm on initial folder creation.
+ // For more than 2 items in the folder, the ranking algorithm for finding
+ // candidate folder name should be rewritten.
+ if (workspaceItemInfos.size() == 2) {
+ ComponentName cmp1 = workspaceItemInfos.get(0).getTargetComponent();
+ ComponentName cmp2 = workspaceItemInfos.get(1).getTargetComponent();
+
+ String pkgName0 = cmp1 == null ? "" : cmp1.getPackageName();
+ String pkgName1 = cmp2 == null ? "" : cmp2.getPackageName();
+ // If the two icons are from the same package,
+ // then assign the main icon's name
+ if (pkgName0.equals(pkgName1)) {
+ WorkspaceItemInfo wInfo0 = workspaceItemInfos.get(0);
+ WorkspaceItemInfo wInfo1 = workspaceItemInfos.get(1);
+ if (workspaceItemInfos.get(0).itemType == Favorites.ITEM_TYPE_APPLICATION) {
+ suggestName[0] = wInfo0.title;
+ } else if (wInfo1.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+ suggestName[0] = wInfo1.title;
+ }
+ return suggestName[0];
+ // two icons are all shortcuts. Don't assign title
+ }
+ }
+ return suggestName[0];
+ }
+}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 54b363e..3b5fd59 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -277,6 +277,7 @@
page.removeAllViews();
pages.add(page);
}
+ mOrganizer.setFolderInfo(mFolder.getInfo());
setupContentDimensions(itemCount);
Iterator<CellLayout> pageItr = pages.iterator();
@@ -285,7 +286,6 @@
int position = 0;
int rank = 0;
- mOrganizer.setFolderInfo(mFolder.getInfo());
for (int i = 0; i < itemCount; i++) {
View v = list.size() > i ? list.get(i) : null;
if (currentPage == null || position >= mOrganizer.getMaxItemsPerPage()) {
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index 9b75b43..598792a 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -44,6 +44,7 @@
public class LoggerUtils {
private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>();
private static final String UNKNOWN = "UNKNOWN";
+ private static final int DEFAULT_PREDICTED_RANK = -100;
public static String getFieldName(int value, Class c) {
SparseArray<String> cache;
@@ -90,7 +91,7 @@
}
public static String getTargetStr(Target t) {
- if (t == null){
+ if (t == null) {
return "";
}
String str = "";
@@ -137,17 +138,16 @@
if (t.intentHash != 0) {
typeStr += ", intentHash=" + t.intentHash;
}
- if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0) &&
- t.itemType != ItemType.TASK) {
+ if (t.itemType == ItemType.FOLDER_ICON) {
+ typeStr += ", grid(" + t.gridX + "," + t.gridY + ")";
+ } else if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0)
+ && t.itemType != ItemType.TASK) {
typeStr += ", predictiveRank=" + t.predictedRank + ", grid(" + t.gridX + "," + t.gridY
- + "), span(" + t.spanX + "," + t.spanY
- + "), pageIdx=" + t.pageIndex;
-
+ + "), span(" + t.spanX + "," + t.spanY + "), pageIdx=" + t.pageIndex;
}
if (t.searchQueryLength != 0) {
typeStr += ", searchQueryLength=" + t.searchQueryLength;
}
-
if (t.itemType == ItemType.TASK) {
typeStr += ", pageIdx=" + t.pageIndex;
}
@@ -168,17 +168,17 @@
public static Target newItemTarget(ItemInfo info, InstantAppResolver instantAppResolver) {
Target t = newTarget(Target.Type.ITEM);
-
switch (info.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
t.itemType = (instantAppResolver != null && info instanceof AppInfo
- && instantAppResolver.isInstantApp(((AppInfo) info)) )
+ && instantAppResolver.isInstantApp(((AppInfo) info)))
? ItemType.WEB_APP
: ItemType.APP_ICON;
- t.predictedRank = -100; // Never assigned
+ t.predictedRank = DEFAULT_PREDICTED_RANK;
break;
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
t.itemType = ItemType.SHORTCUT;
+ t.predictedRank = DEFAULT_PREDICTED_RANK;
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
t.itemType = ItemType.FOLDER_ICON;
@@ -188,6 +188,7 @@
break;
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
t.itemType = ItemType.DEEPSHORTCUT;
+ t.predictedRank = DEFAULT_PREDICTED_RANK;
break;
}
return t;
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 21ca74e..99906fe 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -26,6 +26,8 @@
import static com.android.launcher3.logging.LoggerUtils.newTarget;
import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
+import static java.util.Optional.ofNullable;
+
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -35,6 +37,7 @@
import android.util.Log;
import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.DropTarget;
@@ -58,7 +61,7 @@
/**
* Manages the creation of {@link LauncherEvent}.
* To debug this class, execute following command before side loading a new apk.
- *
+ * <p>
* $ adb shell setprop log.tag.UserEvent VERBOSE
*/
public class UserEventDispatcher implements ResourceBasedOverride {
@@ -94,19 +97,26 @@
/**
* Fills in the container data on the given event if the given view is not null.
+ *
* @return whether container data was added.
*/
- public static boolean fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v) {
+ public boolean fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v) {
// Fill in grid(x,y), pageIndex of the child and container type of the parent
LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v);
if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
return false;
}
- ItemInfo itemInfo = (ItemInfo) v.getTag();
- provider.fillInLogContainerData(v, itemInfo, event.srcTarget[0], event.srcTarget[1]);
+ final ItemInfo itemInfo = (ItemInfo) v.getTag();
+ final Target target = event.srcTarget[0];
+ final Target targetParent = event.srcTarget[1];
+ onFillInLogContainerData(itemInfo, target, targetParent);
+ provider.fillInLogContainerData(v, itemInfo, target, targetParent);
return true;
}
+ protected void onFillInLogContainerData(
+ @NonNull ItemInfo itemInfo, @NonNull Target target, @NonNull Target targetParent) { }
+
private boolean mSessionStarted;
private long mElapsedContainerMillis;
private long mElapsedSessionMillis;
@@ -139,7 +149,11 @@
mAppOrTaskLaunch = true;
}
- public void logActionTip(int actionType, int viewType) { }
+ /**
+ * Dummy method.
+ */
+ public void logActionTip(int actionType, int viewType) {
+ }
@Deprecated
public void logTaskLaunchOrDismiss(int action, int direction, int taskIndex,
@@ -184,15 +198,15 @@
public void logActionCommand(int command, int srcContainerType, int dstContainerType) {
logActionCommand(command, newContainerTarget(srcContainerType),
- dstContainerType >=0 ? newContainerTarget(dstContainerType) : null);
+ dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null);
}
public void logActionCommand(int command, int srcContainerType, int dstContainerType,
- int pageIndex) {
+ int pageIndex) {
Target srcTarget = newContainerTarget(srcContainerType);
srcTarget.pageIndex = pageIndex;
logActionCommand(command, srcTarget,
- dstContainerType >=0 ? newContainerTarget(dstContainerType) : null);
+ dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null);
}
public void logActionCommand(int command, Target srcTarget, Target dstTarget) {
@@ -241,7 +255,7 @@
}
public void logActionOnControl(int action, int controlType, int parentContainer,
- int grandParentContainer){
+ int grandParentContainer) {
LauncherEvent event = newLauncherEvent(newTouchAction(action),
newControlTarget(controlType),
newContainerTarget(parentContainer),
@@ -250,11 +264,11 @@
}
public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer,
- int parentContainerType) {
+ int parentContainerType) {
final LauncherEvent event = (controlInContainer == null && parentContainerType < 0)
? newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL))
: newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL),
- newTarget(Target.Type.CONTAINER));
+ newTarget(Target.Type.CONTAINER));
event.srcTarget[0].controlType = controlType;
if (controlInContainer != null) {
fillInLogContainerData(event, controlInContainer);
@@ -301,9 +315,9 @@
* (1) WORKSPACE: if the launcher is the foreground activity
* (2) APP: if another app was the foreground activity
*/
- public void logStateChangeAction(int action, int dir, int downX, int downY, int srcChildTargetType,
- int srcParentContainerType, int dstContainerType,
- int pageIndex) {
+ public void logStateChangeAction(int action, int dir, int downX, int downY,
+ int srcChildTargetType, int srcParentContainerType, int dstContainerType,
+ int pageIndex) {
LauncherEvent event;
if (srcChildTargetType == LauncherLogProto.ItemType.TASK) {
event = newLauncherEvent(newTouchAction(action),
@@ -326,9 +340,25 @@
}
public void logActionOnItem(int action, int dir, int itemType) {
+ logActionOnItem(action, dir, itemType, null, null);
+ }
+
+ /**
+ * Creates new {@link LauncherEvent} of ITEM target type with input arguments and dispatches it.
+ *
+ * @param touchAction ENUM value of {@link LauncherLogProto.Action.Touch} Action
+ * @param dir ENUM value of {@link LauncherLogProto.Action.Direction} Action
+ * @param itemType ENUM value of {@link LauncherLogProto.ItemType}
+ * @param gridX Nullable X coordinate of item's position on the workspace grid
+ * @param gridY Nullable Y coordinate of item's position on the workspace grid
+ */
+ public void logActionOnItem(int touchAction, int dir, int itemType,
+ @Nullable Integer gridX, @Nullable Integer gridY) {
Target itemTarget = newTarget(Target.Type.ITEM);
itemTarget.itemType = itemType;
- LauncherEvent event = newLauncherEvent(newTouchAction(action), itemTarget);
+ ofNullable(gridX).ifPresent(value -> itemTarget.gridX = value);
+ ofNullable(gridY).ifPresent(value -> itemTarget.gridY = value);
+ LauncherEvent event = newLauncherEvent(newTouchAction(touchAction), itemTarget);
event.action.dir = dir;
dispatchUserEvent(event, null);
}
@@ -351,7 +381,7 @@
LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
newTarget(Target.Type.CONTAINER));
- event.destTarget = new Target[] {
+ event.destTarget = new Target[]{
newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
newDropTarget(dropTargetAsView)
};
@@ -373,14 +403,10 @@
int actionTouch = isButton ? Action.Touch.TAP : Action.Touch.SWIPE;
Action action = newCommandAction(actionTouch);
action.command = Action.Command.BACK;
- action.dir = isButton
- ? Action.Direction.NONE
- : gestureSwipeLeft
- ? Action.Direction.LEFT
- : Action.Direction.RIGHT;
- Target target = newControlTarget(isButton
- ? LauncherLogProto.ControlType.BACK_BUTTON
- : LauncherLogProto.ControlType.BACK_GESTURE);
+ action.dir = isButton ? Action.Direction.NONE :
+ gestureSwipeLeft ? Action.Direction.LEFT : Action.Direction.RIGHT;
+ Target target = newControlTarget(isButton ? LauncherLogProto.ControlType.BACK_BUTTON :
+ LauncherLogProto.ControlType.BACK_GESTURE);
target.spanX = downX;
target.spanY = downY;
target.cardinality = completed ? 1 : 0;
@@ -391,6 +417,7 @@
/**
* Currently logs following containers: workspace, allapps, widget tray.
+ *
* @param reason
*/
public final void resetElapsedContainerMillis(String reason) {
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index dfd5a70..b8c583c 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -76,6 +76,11 @@
if (shortcutExists(dataModel, item.getIntent(), item.user)) {
continue;
}
+
+ // b/139663018 Short-circuit this logic if the icon is a system app
+ if (PackageManagerHelper.isSystemApp(app.getContext(), item.getIntent())) {
+ continue;
+ }
}
if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
@@ -117,25 +122,39 @@
}
SessionInfo sessionInfo = packageInstaller.getActiveSessionInfo(item.user,
packageName);
+ List<LauncherActivityInfo> activities = launcherApps
+ .getActivityList(packageName, item.user);
+ boolean hasActivity = activities != null && !activities.isEmpty();
+
if (sessionInfo == null) {
- List<LauncherActivityInfo> activities = launcherApps
- .getActivityList(packageName, item.user);
- if (activities != null && !activities.isEmpty()) {
- // App was installed while launcher was in the background.
- itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user)
- .makeWorkspaceItem();
- WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo;
- wii.title = "";
- wii.applyFrom(app.getIconCache().getDefaultIcon(item.user));
- app.getIconCache().getTitleAndIcon(wii,
- ((WorkspaceItemInfo) itemInfo).usingLowResIcon());
- } else {
+ if (!hasActivity) {
// Session was cancelled, do not add.
continue;
}
} else {
workspaceInfo.setInstallProgress((int) sessionInfo.getProgress());
}
+
+ if (hasActivity) {
+ // App was installed while launcher was in the background,
+ // or app was already installed for another user.
+ itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user)
+ .makeWorkspaceItem();
+
+ if (shortcutExists(dataModel, itemInfo.getIntent(), itemInfo.user)) {
+ // We need this additional check here since we treat all auto added
+ // workspace items as promise icons. At this point we now have the
+ // correct intent to compare against existing workspace icons.
+ // Icon already exists on the workspace and should not be auto-added.
+ continue;
+ }
+
+ WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo;
+ wii.title = "";
+ wii.applyFrom(app.getIconCache().getDefaultIcon(item.user));
+ app.getIconCache().getTitleAndIcon(wii,
+ ((WorkspaceItemInfo) itemInfo).usingLowResIcon());
+ }
}
// Add the shortcut to the db
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 717a7e9..021fb30 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -16,7 +16,7 @@
package com.android.launcher3.notification;
-import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
import android.app.Notification;
import android.content.Context;
@@ -30,7 +30,7 @@
import com.android.launcher3.R;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.Themes;
import java.util.List;
@@ -49,7 +49,7 @@
private final TextView mHeaderCount;
private final NotificationMainView mMainView;
private final NotificationFooterLayout mFooter;
- private final SwipeDetector mSwipeDetector;
+ private final SingleAxisSwipeDetector mSwipeDetector;
private final View mIconView;
private final View mHeader;
@@ -74,8 +74,8 @@
mHeader = container.findViewById(R.id.header);
mDivider = container.findViewById(R.id.divider);
- mSwipeDetector = new SwipeDetector(mContext, mMainView, HORIZONTAL);
- mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
+ mSwipeDetector = new SingleAxisSwipeDetector(mContext, mMainView, HORIZONTAL);
+ mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_BOTH, false);
mMainView.setSwipeDetector(mSwipeDetector);
mFooter.setContainer(this);
}
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 78627ec..b67adbb 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -38,8 +38,9 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.OverScroll;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.Themes;
@@ -48,7 +49,7 @@
* e.g. icon + title + text.
*/
@TargetApi(Build.VERSION_CODES.N)
-public class NotificationMainView extends FrameLayout implements SwipeDetector.Listener {
+public class NotificationMainView extends FrameLayout implements SingleAxisSwipeDetector.Listener {
private static FloatProperty<NotificationMainView> CONTENT_TRANSLATION =
new FloatProperty<NotificationMainView>("contentTranslation") {
@@ -75,7 +76,7 @@
private TextView mTextView;
private View mIconView;
- private SwipeDetector mSwipeDetector;
+ private SingleAxisSwipeDetector mSwipeDetector;
public NotificationMainView(Context context) {
this(context, null, 0);
@@ -107,7 +108,7 @@
mIconView = findViewById(R.id.popup_item_icon);
}
- public void setSwipeDetector(SwipeDetector swipeDetector) {
+ public void setSwipeDetector(SingleAxisSwipeDetector swipeDetector) {
mSwipeDetector = swipeDetector;
}
@@ -173,7 +174,7 @@
LauncherLogProto.ItemType.NOTIFICATION);
}
- // SwipeDetector.Listener's
+ // SingleAxisSwipeDetector.Listener's
@Override
public void onDragStart(boolean start) { }
@@ -187,7 +188,7 @@
}
@Override
- public void onDragEnd(float velocity, boolean fling) {
+ public void onDragEnd(float velocity) {
final boolean willExit;
final float endTranslation;
final float startTranslation = mTextAndBackground.getTranslationX();
@@ -195,7 +196,7 @@
if (!canChildBeDismissed()) {
willExit = false;
endTranslation = 0;
- } else if (fling) {
+ } else if (mSwipeDetector.isFling(velocity)) {
willExit = true;
endTranslation = velocity < 0 ? - getWidth() : getWidth();
} else if (Math.abs(startTranslation) > getWidth() / 2) {
@@ -206,7 +207,7 @@
endTranslation = 0;
}
- long duration = SwipeDetector.calculateDuration(velocity,
+ long duration = BaseSwipeDetector.calculateDuration(velocity,
(endTranslation - startTranslation) / getWidth());
mContentTranslateAnimator.removeAllListeners();
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 28000b9..98f7fd8 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -360,10 +360,14 @@
final TimeInterpolator revealInterpolator = ACCEL_DEACCEL;
// Rectangular reveal.
+ mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
.createRevealAnimator(this, false);
revealAnim.setDuration(revealDuration);
revealAnim.setInterpolator(revealInterpolator);
+ // Clip the popup to the initial outline while the notification dot and arrow animate.
+ revealAnim.start();
+ revealAnim.pause();
ValueAnimator fadeIn = ValueAnimator.ofFloat(0, 1);
fadeIn.setDuration(revealDuration + arrowDuration);
@@ -399,7 +403,6 @@
if (!mIsOpen) {
return;
}
- mEndRect.setEmpty();
if (getOutlineProvider() instanceof RevealOutlineAnimation) {
((RevealOutlineAnimation) getOutlineProvider()).getOutline(mEndRect);
}
@@ -471,9 +474,6 @@
mStartRect.set(arrowCenterX - halfArrowWidth, arrowCenterY, arrowCenterX + halfArrowWidth,
arrowCenterY);
- if (mEndRect.isEmpty()) {
- mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
- }
return new RoundedRectRevealOutlineProvider
(arrowCornerRadius, mOutlineRadius, mStartRect, mEndRect);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 4833c26..e8ac1d4 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -575,8 +575,11 @@
@Override
protected void closeComplete() {
- mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
- mOriginalIcon.setForceHideDot(false);
+ PopupContainerWithArrow openPopup = getOpen(mLauncher);
+ if (openPopup == null || openPopup.mOriginalIcon != mOriginalIcon) {
+ mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
+ mOriginalIcon.setForceHideDot(false);
+ }
super.closeComplete();
}
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index cc2d586..c6de9ca 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -151,6 +151,12 @@
mLeaks.add(bitmap);
break;
}
+
+ case TestProtocol.REQUEST_ICON_HEIGHT: {
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ mDeviceProfile.allAppsCellHeightPx);
+ break;
+ }
}
return response;
}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 62bb564..07ddbdc 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -66,6 +66,8 @@
"all-apps-to-overview-swipe-height";
public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT =
"home-to-all-apps-swipe-height";
+ public static final String REQUEST_ICON_HEIGHT =
+ "icon-height";
public static final String REQUEST_HOTSEAT_TOP = "hotseat-top";
public static final String REQUEST_IS_LAUNCHER_INITIALIZED = "is-launcher-initialized";
public static final String REQUEST_FREEZE_APP_LIST = "freeze-app-list";
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index c5ba5ba..f40f976 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -53,7 +53,7 @@
* TouchController for handling state changes
*/
public abstract class AbstractStateChangeTouchController
- implements TouchController, SwipeDetector.Listener {
+ implements TouchController, SingleAxisSwipeDetector.Listener {
// Progress after which the transition is assumed to be a success in case user does not fling
public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
@@ -65,8 +65,8 @@
protected final long ATOMIC_DURATION = getAtomicDuration();
protected final Launcher mLauncher;
- protected final SwipeDetector mDetector;
- protected final SwipeDetector.Direction mSwipeDirection;
+ protected final SingleAxisSwipeDetector mDetector;
+ protected final SingleAxisSwipeDetector.Direction mSwipeDirection;
private boolean mNoIntercept;
private boolean mIsLogContainerSet;
@@ -101,9 +101,9 @@
private float mAtomicComponentsStartProgress;
- public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
+ public AbstractStateChangeTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) {
mLauncher = l;
- mDetector = new SwipeDetector(l, this, dir);
+ mDetector = new SingleAxisSwipeDetector(l, this, dir);
mSwipeDirection = dir;
}
@@ -127,7 +127,7 @@
boolean ignoreSlopWhenSettling = false;
if (mCurrentAnimation != null) {
- directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+ directionsToDetectScroll = SingleAxisSwipeDetector.DIRECTION_BOTH;
ignoreSlopWhenSettling = true;
} else {
directionsToDetectScroll = getSwipeDirection();
@@ -152,10 +152,10 @@
LauncherState fromState = mLauncher.getStateManager().getState();
int swipeDirection = 0;
if (getTargetState(fromState, true /* isDragTowardPositive */) != fromState) {
- swipeDirection |= SwipeDetector.DIRECTION_POSITIVE;
+ swipeDirection |= SingleAxisSwipeDetector.DIRECTION_POSITIVE;
}
if (getTargetState(fromState, false /* isDragTowardPositive */) != fromState) {
- swipeDirection |= SwipeDetector.DIRECTION_NEGATIVE;
+ swipeDirection |= SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
}
return swipeDirection;
}
@@ -369,7 +369,8 @@
}
@Override
- public void onDragEnd(float velocity, boolean fling) {
+ public void onDragEnd(float velocity) {
+ boolean fling = mDetector.isFling(velocity);
final int logAction = fling ? Touch.FLING : Touch.SWIPE;
boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
@@ -406,16 +407,13 @@
} else {
startProgress = Utilities.boundToRange(progress
+ velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
- duration = SwipeDetector.calculateDuration(velocity,
+ duration = BaseSwipeDetector.calculateDuration(velocity,
endProgress - Math.max(progress, 0)) * durationMultiplier;
}
} 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) {
@@ -424,7 +422,7 @@
} else {
startProgress = Utilities.boundToRange(progress
+ velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
- duration = SwipeDetector.calculateDuration(velocity,
+ duration = BaseSwipeDetector.calculateDuration(velocity,
Math.min(progress, 1) - endProgress) * durationMultiplier;
}
}
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
new file mode 100644
index 0000000..12ca5ee
--- /dev/null
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.touch;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import android.graphics.PointF;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Scroll/drag/swipe gesture detector.
+ *
+ * Definition of swipe is different from android system in that this detector handles
+ * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before
+ * swipe action happens.
+ *
+ * @see SingleAxisSwipeDetector
+ * @see BothAxesSwipeDetector
+ */
+public abstract class BaseSwipeDetector {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "BaseSwipeDetector";
+ private static final float ANIMATION_DURATION = 1200;
+ /** The minimum release velocity in pixels per millisecond that triggers fling.*/
+ private static final float RELEASE_VELOCITY_PX_MS = 1.0f;
+ private static final PointF sTempPoint = new PointF();
+
+ private final PointF mDownPos = new PointF();
+ private final PointF mLastPos = new PointF();
+ protected final boolean mIsRtl;
+ protected final float mTouchSlop;
+ protected final float mMaxVelocity;
+
+ private int mActivePointerId = INVALID_POINTER_ID;
+ private VelocityTracker mVelocityTracker;
+ private PointF mLastDisplacement = new PointF();
+ private PointF mDisplacement = new PointF();
+ protected PointF mSubtractDisplacement = new PointF();
+ private ScrollState mState = ScrollState.IDLE;
+
+ protected boolean mIgnoreSlopWhenSettling;
+
+ private enum ScrollState {
+ IDLE,
+ DRAGGING, // onDragStart, onDrag
+ SETTLING // onDragEnd
+ }
+
+ protected BaseSwipeDetector(@NonNull ViewConfiguration config, boolean isRtl) {
+ mTouchSlop = config.getScaledTouchSlop();
+ mMaxVelocity = config.getScaledMaximumFlingVelocity();
+ mIsRtl = isRtl;
+ }
+
+ public static long calculateDuration(float velocity, float progressNeeded) {
+ // TODO: make these values constants after tuning.
+ float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
+ float travelDistance = Math.max(0.2f, progressNeeded);
+ long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
+ if (DBG) {
+ Log.d(TAG, String.format(
+ "calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
+ }
+ return duration;
+ }
+
+ public int getDownX() {
+ return (int) mDownPos.x;
+ }
+
+ public int getDownY() {
+ return (int) mDownPos.y;
+ }
+ /**
+ * There's no touch and there's no animation.
+ */
+ public boolean isIdleState() {
+ return mState == ScrollState.IDLE;
+ }
+
+ public boolean isSettlingState() {
+ return mState == ScrollState.SETTLING;
+ }
+
+ public boolean isDraggingState() {
+ return mState == ScrollState.DRAGGING;
+ }
+
+ public boolean isDraggingOrSettling() {
+ return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
+ }
+
+ public void finishedScrolling() {
+ setState(ScrollState.IDLE);
+ }
+
+ public boolean isFling(float velocity) {
+ return Math.abs(velocity) > RELEASE_VELOCITY_PX_MS;
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ int actionMasked = ev.getActionMasked();
+ if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ switch (actionMasked) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = ev.getPointerId(0);
+ mDownPos.set(ev.getX(), ev.getY());
+ mLastPos.set(mDownPos);
+ mLastDisplacement.set(0, 0);
+ mDisplacement.set(0, 0);
+
+ if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+ setState(ScrollState.DRAGGING);
+ }
+ break;
+ //case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ int ptrIdx = ev.getActionIndex();
+ int ptrId = ev.getPointerId(ptrIdx);
+ if (ptrId == mActivePointerId) {
+ final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+ mDownPos.set(
+ ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+ ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+ mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+ mActivePointerId = ev.getPointerId(newPointerIdx);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == INVALID_POINTER_ID) {
+ break;
+ }
+ mDisplacement.set(ev.getX(pointerIndex) - mDownPos.x,
+ ev.getY(pointerIndex) - mDownPos.y);
+ if (mIsRtl) {
+ mDisplacement.x = -mDisplacement.x;
+ }
+
+ // handle state and listener calls.
+ if (mState != ScrollState.DRAGGING && shouldScrollStart(mDisplacement)) {
+ setState(ScrollState.DRAGGING);
+ }
+ if (mState == ScrollState.DRAGGING) {
+ reportDragging(ev);
+ }
+ mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ // These are synthetic events and there is no need to update internal values.
+ if (mState == ScrollState.DRAGGING) {
+ setState(ScrollState.SETTLING);
+ }
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ //------------------- ScrollState transition diagram -----------------------------------
+ //
+ // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING
+ // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
+ // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
+ // SETTLING -> (View settled) -> IDLE
+
+ private void setState(ScrollState newState) {
+ if (DBG) {
+ Log.d(TAG, "setState:" + mState + "->" + newState);
+ }
+ // onDragStart and onDragEnd is reported ONLY on state transition
+ if (newState == ScrollState.DRAGGING) {
+ initializeDragging();
+ if (mState == ScrollState.IDLE) {
+ reportDragStart(false /* recatch */);
+ } else if (mState == ScrollState.SETTLING) {
+ reportDragStart(true /* recatch */);
+ }
+ }
+ if (newState == ScrollState.SETTLING) {
+ reportDragEnd();
+ }
+
+ mState = newState;
+ }
+
+ private void initializeDragging() {
+ if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+ mSubtractDisplacement.set(0, 0);
+ } else {
+ mSubtractDisplacement.x = mDisplacement.x > 0 ? mTouchSlop : -mTouchSlop;
+ mSubtractDisplacement.y = mDisplacement.y > 0 ? mTouchSlop : -mTouchSlop;
+ }
+ }
+
+ protected abstract boolean shouldScrollStart(PointF displacement);
+
+ private void reportDragStart(boolean recatch) {
+ reportDragStartInternal(recatch);
+ if (DBG) {
+ Log.d(TAG, "onDragStart recatch:" + recatch);
+ }
+ }
+
+ protected abstract void reportDragStartInternal(boolean recatch);
+
+ private void reportDragging(MotionEvent event) {
+ if (mDisplacement != mLastDisplacement) {
+ if (DBG) {
+ Log.d(TAG, String.format("onDrag disp=%s", mDisplacement));
+ }
+
+ mLastDisplacement.set(mDisplacement);
+ sTempPoint.set(mDisplacement.x - mSubtractDisplacement.x,
+ mDisplacement.y - mSubtractDisplacement.y);
+ reportDraggingInternal(sTempPoint, event);
+ }
+ }
+
+ protected abstract void reportDraggingInternal(PointF displacement, MotionEvent event);
+
+ private void reportDragEnd() {
+ mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
+ PointF velocity = new PointF(mVelocityTracker.getXVelocity() / 1000,
+ mVelocityTracker.getYVelocity() / 1000);
+ if (mIsRtl) {
+ velocity.x = -velocity.x;
+ }
+ if (DBG) {
+ Log.d(TAG, String.format("onScrollEnd disp=%.1s, velocity=%.1s",
+ mDisplacement, velocity));
+ }
+
+ reportDragEndInternal(velocity);
+ }
+
+ protected abstract void reportDragEndInternal(PointF velocity);
+}
diff --git a/src/com/android/launcher3/touch/BothAxesSwipeDetector.java b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
new file mode 100644
index 0000000..944391e
--- /dev/null
+++ b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
@@ -0,0 +1,99 @@
+/*
+ * 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.touch;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Two dimensional scroll/drag/swipe gesture detector that reports x and y displacement/velocity.
+ */
+public class BothAxesSwipeDetector extends BaseSwipeDetector {
+
+ public static final int DIRECTION_UP = 1 << 0;
+ // Note that this will track left instead of right in RTL.
+ public static final int DIRECTION_RIGHT = 1 << 1;
+ public static final int DIRECTION_DOWN = 1 << 2;
+ // Note that this will track right instead of left in RTL.
+ public static final int DIRECTION_LEFT = 1 << 3;
+
+ /* Client of this gesture detector can register a callback. */
+ private final Listener mListener;
+
+ private int mScrollDirections;
+
+ public BothAxesSwipeDetector(@NonNull Context context, @NonNull Listener l) {
+ this(ViewConfiguration.get(context), l, Utilities.isRtl(context.getResources()));
+ }
+
+ @VisibleForTesting
+ protected BothAxesSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
+ boolean isRtl) {
+ super(config, isRtl);
+ mListener = l;
+ }
+
+ public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+ mScrollDirections = scrollDirectionFlags;
+ mIgnoreSlopWhenSettling = ignoreSlop;
+ }
+
+ @Override
+ protected boolean shouldScrollStart(PointF displacement) {
+ // Check if the client is interested in scroll in current direction.
+ boolean canScrollUp = (mScrollDirections & DIRECTION_UP) > 0
+ && displacement.y <= -mTouchSlop;
+ boolean canScrollRight = (mScrollDirections & DIRECTION_RIGHT) > 0
+ && displacement.x >= mTouchSlop;
+ boolean canScrollDown = (mScrollDirections & DIRECTION_DOWN) > 0
+ && displacement.y >= mTouchSlop;
+ boolean canScrollLeft = (mScrollDirections & DIRECTION_LEFT) > 0
+ && displacement.x <= -mTouchSlop;
+ return canScrollUp || canScrollRight || canScrollDown || canScrollLeft;
+ }
+
+ @Override
+ protected void reportDragStartInternal(boolean recatch) {
+ mListener.onDragStart(!recatch);
+ }
+
+ @Override
+ protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
+ mListener.onDrag(displacement, event);
+ }
+
+ @Override
+ protected void reportDragEndInternal(PointF velocity) {
+ mListener.onDragEnd(velocity);
+ }
+
+ /** Listener to receive updates on the swipe. */
+ public interface Listener {
+ /** @param start whether this was the original drag start, as opposed to a recatch. */
+ void onDragStart(boolean start);
+
+ boolean onDrag(PointF displacement, MotionEvent motionEvent);
+
+ void onDragEnd(PointF velocity);
+ }
+}
diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
new file mode 100644
index 0000000..f2ebc45
--- /dev/null
+++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
@@ -0,0 +1,190 @@
+/*
+ * 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.touch;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * One dimensional scroll/drag/swipe gesture detector (either HORIZONTAL or VERTICAL).
+ */
+public class SingleAxisSwipeDetector extends BaseSwipeDetector {
+
+ public static final int DIRECTION_POSITIVE = 1 << 0;
+ public static final int DIRECTION_NEGATIVE = 1 << 1;
+ public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
+
+ public static final Direction VERTICAL = new Direction() {
+
+ @Override
+ boolean isPositive(float displacement) {
+ // Up
+ return displacement < 0;
+ }
+
+ @Override
+ boolean isNegative(float displacement) {
+ // Down
+ return displacement > 0;
+ }
+
+ @Override
+ float extractDirection(PointF direction) {
+ return direction.y;
+ }
+
+ @Override
+ boolean canScrollStart(PointF displacement, float touchSlop) {
+ return Math.abs(displacement.y) >= Math.max(Math.abs(displacement.x), touchSlop);
+ }
+
+ };
+
+ public static final Direction HORIZONTAL = new Direction() {
+
+ @Override
+ boolean isPositive(float displacement) {
+ // Right
+ return displacement > 0;
+ }
+
+ @Override
+ boolean isNegative(float displacement) {
+ // Left
+ return displacement < 0;
+ }
+
+ @Override
+ float extractDirection(PointF direction) {
+ return direction.x;
+ }
+
+ @Override
+ boolean canScrollStart(PointF displacement, float touchSlop) {
+ return Math.abs(displacement.x) >= Math.max(Math.abs(displacement.y), touchSlop);
+ }
+ };
+
+ private final Direction mDir;
+ /* Client of this gesture detector can register a callback. */
+ private final Listener mListener;
+
+ private int mScrollDirections;
+
+ public SingleAxisSwipeDetector(@NonNull Context context, @NonNull Listener l,
+ @NonNull Direction dir) {
+ this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
+ }
+
+ @VisibleForTesting
+ protected SingleAxisSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
+ @NonNull Direction dir, boolean isRtl) {
+ super(config, isRtl);
+ mListener = l;
+ mDir = dir;
+ }
+
+ public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+ mScrollDirections = scrollDirectionFlags;
+ mIgnoreSlopWhenSettling = ignoreSlop;
+ }
+
+ public int getScrollDirections() {
+ return mScrollDirections;
+ }
+
+ /**
+ * Returns if the start drag was towards the positive direction or negative.
+ *
+ * @see #setDetectableScrollConditions(int, boolean)
+ * @see #DIRECTION_BOTH
+ */
+ public boolean wasInitialTouchPositive() {
+ return mDir.isPositive(mDir.extractDirection(mSubtractDisplacement));
+ }
+
+ @Override
+ protected boolean shouldScrollStart(PointF displacement) {
+ // Reject cases where the angle or slop condition is not met.
+ if (!mDir.canScrollStart(displacement, mTouchSlop)) {
+ return false;
+ }
+
+ // Check if the client is interested in scroll in current direction.
+ float displacementComponent = mDir.extractDirection(displacement);
+ return canScrollNegative(displacementComponent) || canScrollPositive(displacementComponent);
+ }
+
+ private boolean canScrollNegative(float displacement) {
+ return (mScrollDirections & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(displacement);
+ }
+
+ private boolean canScrollPositive(float displacement) {
+ return (mScrollDirections & DIRECTION_POSITIVE) > 0 && mDir.isPositive(displacement);
+ }
+
+ @Override
+ protected void reportDragStartInternal(boolean recatch) {
+ mListener.onDragStart(!recatch);
+ }
+
+ @Override
+ protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
+ mListener.onDrag(mDir.extractDirection(displacement), event);
+ }
+
+ @Override
+ protected void reportDragEndInternal(PointF velocity) {
+ float velocityComponent = mDir.extractDirection(velocity);
+ mListener.onDragEnd(velocityComponent);
+ }
+
+ /** Listener to receive updates on the swipe. */
+ public interface Listener {
+ /** @param start whether this was the original drag start, as opposed to a recatch. */
+ void onDragStart(boolean start);
+
+ // TODO remove
+ boolean onDrag(float displacement);
+
+ default boolean onDrag(float displacement, MotionEvent event) {
+ return onDrag(displacement);
+ }
+
+ void onDragEnd(float velocity);
+ }
+
+ public abstract static class Direction {
+
+ abstract boolean isPositive(float displacement);
+
+ abstract boolean isNegative(float displacement);
+
+ /** Returns the part of the given {@link PointF} that is relevant to this direction. */
+ abstract float extractDirection(PointF point);
+
+ /** Reject cases where the angle or slop condition is not met. */
+ abstract boolean canScrollStart(PointF displacement, float touchSlop);
+
+ }
+}
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
deleted file mode 100644
index c38ca24..0000000
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.touch;
-
-import static android.view.MotionEvent.INVALID_POINTER_ID;
-
-import android.content.Context;
-import android.graphics.PointF;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.launcher3.Utilities;
-
-/**
- * One dimensional scroll/drag/swipe gesture detector.
- *
- * Definition of swipe is different from android system in that this detector handles
- * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before
- * swipe action happens
- */
-public class SwipeDetector {
-
- private static final boolean DBG = false;
- private static final String TAG = "SwipeDetector";
- private static final float ANIMATION_DURATION = 1200;
- /** The minimum release velocity in pixels per millisecond that triggers fling.*/
- private static final float RELEASE_VELOCITY_PX_MS = 1.0f;
-
- public static final int DIRECTION_POSITIVE = 1 << 0;
- public static final int DIRECTION_NEGATIVE = 1 << 1;
- public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
-
- public static final Direction VERTICAL = new Direction() {
-
- @Override
- float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, boolean isRtl) {
- return ev.getY(pointerIndex) - refPoint.y;
- }
-
- @Override
- float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
- return Math.abs(ev.getX(pointerIndex) - downPos.x);
- }
-
- @Override
- float getVelocity(VelocityTracker tracker, boolean isRtl) {
- return tracker.getYVelocity();
- }
-
- @Override
- boolean isPositive(float displacement) {
- // Up
- return displacement < 0;
- }
-
- @Override
- boolean isNegative(float displacement) {
- // Down
- return displacement > 0;
- }
- };
-
- public static final Direction HORIZONTAL = new Direction() {
-
- @Override
- float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, boolean isRtl) {
- float displacement = ev.getX(pointerIndex) - refPoint.x;
- if (isRtl) {
- displacement = -displacement;
- }
- return displacement;
- }
-
- @Override
- float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
- return Math.abs(ev.getY(pointerIndex) - downPos.y);
- }
-
- @Override
- float getVelocity(VelocityTracker tracker, boolean isRtl) {
- float velocity = tracker.getXVelocity();
- if (isRtl) {
- velocity = -velocity;
- }
- return velocity;
- }
-
- @Override
- boolean isPositive(float displacement) {
- // Right
- return displacement > 0;
- }
-
- @Override
- boolean isNegative(float displacement) {
- // Left
- return displacement < 0;
- }
- };
-
- private final PointF mDownPos = new PointF();
- private final PointF mLastPos = new PointF();
- private final Direction mDir;
- private final boolean mIsRtl;
- private final float mTouchSlop;
- private final float mMaxVelocity;
- /* Client of this gesture detector can register a callback. */
- private final Listener mListener;
-
- private int mActivePointerId = INVALID_POINTER_ID;
- private VelocityTracker mVelocityTracker;
- private float mLastDisplacement;
- private float mDisplacement;
- private float mSubtractDisplacement;
- private boolean mIgnoreSlopWhenSettling;
- private int mScrollDirections;
- private ScrollState mState = ScrollState.IDLE;
-
- private enum ScrollState {
- IDLE,
- DRAGGING, // onDragStart, onDrag
- SETTLING // onDragEnd
- }
-
- public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
- this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
- }
-
- @VisibleForTesting
- protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
- @NonNull Direction dir, boolean isRtl) {
- mListener = l;
- mDir = dir;
- mIsRtl = isRtl;
- mTouchSlop = config.getScaledTouchSlop();
- mMaxVelocity = config.getScaledMaximumFlingVelocity();
- }
-
- public static long calculateDuration(float velocity, float progressNeeded) {
- // TODO: make these values constants after tuning.
- float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
- float travelDistance = Math.max(0.2f, progressNeeded);
- long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
- if (DBG) {
- Log.d(TAG, String.format(
- "calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
- }
- return duration;
- }
-
- public int getDownX() {
- return (int) mDownPos.x;
- }
-
- public int getDownY() {
- return (int) mDownPos.y;
- }
- /**
- * There's no touch and there's no animation.
- */
- public boolean isIdleState() {
- return mState == ScrollState.IDLE;
- }
-
- public boolean isSettlingState() {
- return mState == ScrollState.SETTLING;
- }
-
- public boolean isDraggingState() {
- return mState == ScrollState.DRAGGING;
- }
-
- public boolean isDraggingOrSettling() {
- return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
- }
-
- public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
- mScrollDirections = scrollDirectionFlags;
- mIgnoreSlopWhenSettling = ignoreSlop;
- }
-
- public int getScrollDirections() {
- return mScrollDirections;
- }
-
- public void finishedScrolling() {
- setState(ScrollState.IDLE);
- }
-
- /**
- * Returns if the start drag was towards the positive direction or negative.
- *
- * @see #setDetectableScrollConditions(int, boolean)
- * @see #DIRECTION_BOTH
- */
- public boolean wasInitialTouchPositive() {
- return mDir.isPositive(mSubtractDisplacement);
- }
-
- public boolean onTouchEvent(MotionEvent ev) {
- int actionMasked = ev.getActionMasked();
- if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
- mVelocityTracker.clear();
- }
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(ev);
-
- switch (actionMasked) {
- case MotionEvent.ACTION_DOWN:
- mActivePointerId = ev.getPointerId(0);
- mDownPos.set(ev.getX(), ev.getY());
- mLastPos.set(mDownPos);
- mLastDisplacement = 0;
- mDisplacement = 0;
-
- if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
- setState(ScrollState.DRAGGING);
- }
- break;
- //case MotionEvent.ACTION_POINTER_DOWN:
- case MotionEvent.ACTION_POINTER_UP:
- int ptrIdx = ev.getActionIndex();
- int ptrId = ev.getPointerId(ptrIdx);
- if (ptrId == mActivePointerId) {
- final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
- mDownPos.set(
- ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
- ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
- mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
- mActivePointerId = ev.getPointerId(newPointerIdx);
- }
- break;
- case MotionEvent.ACTION_MOVE:
- int pointerIndex = ev.findPointerIndex(mActivePointerId);
- if (pointerIndex == INVALID_POINTER_ID) {
- break;
- }
- mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos, mIsRtl);
-
- // handle state and listener calls.
- if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) {
- setState(ScrollState.DRAGGING);
- }
- if (mState == ScrollState.DRAGGING) {
- reportDragging(ev);
- }
- mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- // These are synthetic events and there is no need to update internal values.
- if (mState == ScrollState.DRAGGING) {
- setState(ScrollState.SETTLING);
- }
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- break;
- default:
- break;
- }
- return true;
- }
-
- //------------------- ScrollState transition diagram -----------------------------------
- //
- // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING
- // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
- // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
- // SETTLING -> (View settled) -> IDLE
-
- private void setState(ScrollState newState) {
- if (DBG) {
- Log.d(TAG, "setState:" + mState + "->" + newState);
- }
- // onDragStart and onDragEnd is reported ONLY on state transition
- if (newState == ScrollState.DRAGGING) {
- initializeDragging();
- if (mState == ScrollState.IDLE) {
- reportDragStart(false /* recatch */);
- } else if (mState == ScrollState.SETTLING) {
- reportDragStart(true /* recatch */);
- }
- }
- if (newState == ScrollState.SETTLING) {
- reportDragEnd();
- }
-
- mState = newState;
- }
-
- private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) {
- // reject cases where the angle or slop condition is not met.
- if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop)
- > Math.abs(mDisplacement)) {
- return false;
- }
-
- // Check if the client is interested in scroll in current direction.
- return ((mScrollDirections & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(mDisplacement))
- || ((mScrollDirections & DIRECTION_POSITIVE) > 0 && mDir.isPositive(mDisplacement));
- }
-
- private void reportDragStart(boolean recatch) {
- mListener.onDragStart(!recatch);
- if (DBG) {
- Log.d(TAG, "onDragStart recatch:" + recatch);
- }
- }
-
- private void initializeDragging() {
- if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
- mSubtractDisplacement = 0;
- } else if (mDisplacement > 0) {
- mSubtractDisplacement = mTouchSlop;
- } else {
- mSubtractDisplacement = -mTouchSlop;
- }
- }
-
- private void reportDragging(MotionEvent event) {
- if (mDisplacement != mLastDisplacement) {
- if (DBG) {
- Log.d(TAG, String.format("onDrag disp=%.1f", mDisplacement));
- }
-
- mLastDisplacement = mDisplacement;
- mListener.onDrag(mDisplacement - mSubtractDisplacement, event);
- }
- }
-
- private void reportDragEnd() {
- mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
- float velocity = mDir.getVelocity(mVelocityTracker, mIsRtl) / 1000;
- if (DBG) {
- Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
- mDisplacement, velocity));
- }
-
- mListener.onDragEnd(velocity, Math.abs(velocity) > RELEASE_VELOCITY_PX_MS);
- }
-
- /** Listener to receive updates on the swipe. */
- public interface Listener {
- void onDragStart(boolean start);
-
- boolean onDrag(float displacement);
-
- default boolean onDrag(float displacement, MotionEvent event) {
- return onDrag(displacement);
- }
-
- void onDragEnd(float velocity, boolean fling);
- }
-
- public abstract static class Direction {
-
- abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint,
- boolean isRtl);
-
- /**
- * Distance in pixels a touch can wander before we think the user is scrolling.
- */
- abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
-
- abstract float getVelocity(VelocityTracker tracker, boolean isRtl);
-
- abstract boolean isPositive(float displacement);
-
- abstract boolean isNegative(float displacement);
- }
-}
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index ef4307e..e97adb5 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -236,6 +236,9 @@
} else {
packageName = cn.getPackageName();
}
+ if (packageName == null) {
+ packageName = intent.getPackage();
+ }
if (packageName != null) {
try {
PackageInfo info = pm.getPackageInfo(packageName, 0);
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
new file mode 100644
index 0000000..04741a1
--- /dev/null
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -0,0 +1,84 @@
+/*
+ * 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 static android.os.VibrationEffect.createPredefined;
+import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Build;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.provider.Settings;
+
+/**
+ * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
+ */
+@TargetApi(Build.VERSION_CODES.Q)
+public class VibratorWrapper {
+
+ public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE =
+ new MainThreadInitializedObject<>(VibratorWrapper::new);
+
+ private static final VibrationEffect EFFECT_CLICK =
+ createPredefined(VibrationEffect.EFFECT_CLICK);
+
+ /**
+ * Haptic when entering overview.
+ */
+ public static final VibrationEffect OVERVIEW_HAPTIC = EFFECT_CLICK;
+
+ private final Vibrator mVibrator;
+ private final boolean mHasVibrator;
+
+ private boolean mIsHapticFeedbackEnabled;
+
+ public VibratorWrapper(Context context) {
+ mVibrator = context.getSystemService(Vibrator.class);
+ mHasVibrator = mVibrator.hasVibrator();
+ if (mHasVibrator) {
+ final ContentResolver resolver = context.getContentResolver();
+ mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver);
+ final ContentObserver observer = new ContentObserver(MAIN_EXECUTOR.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver);
+ }
+ };
+ resolver.registerContentObserver(Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED),
+ false /* notifyForDescendents */, observer);
+ } else {
+ mIsHapticFeedbackEnabled = false;
+ }
+ }
+
+ private boolean isHapticFeedbackEnabled(ContentResolver resolver) {
+ return Settings.System.getInt(resolver, HAPTIC_FEEDBACK_ENABLED, 0) == 1;
+ }
+
+ /** Vibrates with the given effect if haptic feedback is available and enabled. */
+ public void vibrate(VibrationEffect vibrationEffect) {
+ if (mHasVibrator && mIsHapticFeedbackEnabled) {
+ UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect));
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java
index 8af048d..5b33f18 100644
--- a/src/com/android/launcher3/util/ViewPool.java
+++ b/src/com/android/launcher3/util/ViewPool.java
@@ -21,12 +21,12 @@
import android.view.View;
import android.view.ViewGroup;
-import com.android.launcher3.util.ViewPool.Reusable;
-
import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import com.android.launcher3.util.ViewPool.Reusable;
+
/**
* Utility class to maintain a pool of reusable views.
* During initialization, views are inflated on the background thread.
@@ -58,14 +58,18 @@
Preconditions.assertUIThread();
Handler handler = new Handler();
+ // LayoutInflater is not thread save as it maintains a global variable 'mConstructorArgs'.
+ // Create a different copy to use on the background thread.
+ LayoutInflater inflater = mInflater.cloneInContext(mInflater.getContext());
+
// Inflate views on a non looper thread. This allows us to catch errors like calling
// "new Handler()" in constructor easily.
new Thread(() -> {
for (int i = 0; i < initialSize; i++) {
- T view = inflateNewView();
+ T view = inflateNewView(inflater);
handler.post(() -> addToPool(view));
}
- }).start();
+ }, "ViewPool-init").start();
}
@UiThread
@@ -94,12 +98,12 @@
mCurrentSize--;
return (T) mPool[mCurrentSize];
}
- return inflateNewView();
+ return inflateNewView(mInflater);
}
@AnyThread
- private T inflateNewView() {
- return (T) mInflater.inflate(mLayoutId, mParent, false);
+ private T inflateNewView(LayoutInflater inflater) {
+ return (T) inflater.inflate(mLayoutId, mParent, false);
}
/**
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index a4518ba..195a77a 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -32,13 +32,14 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.BaseSwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
/**
* Extension of AbstractFloatingView with common methods for sliding in from bottom
*/
public abstract class AbstractSlideInView extends AbstractFloatingView
- implements SwipeDetector.Listener {
+ implements SingleAxisSwipeDetector.Listener {
protected static Property<AbstractSlideInView, Float> TRANSLATION_SHIFT =
new Property<AbstractSlideInView, Float>(Float.class, "translationShift") {
@@ -57,7 +58,7 @@
protected static final float TRANSLATION_SHIFT_OPENED = 0f;
protected final Launcher mLauncher;
- protected final SwipeDetector mSwipeDetector;
+ protected final SingleAxisSwipeDetector mSwipeDetector;
protected final ObjectAnimator mOpenCloseAnimator;
protected View mContent;
@@ -73,7 +74,8 @@
mLauncher = Launcher.getLauncher(context);
mScrollInterpolator = Interpolators.SCROLL_CUBIC;
- mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
+ mSwipeDetector = new SingleAxisSwipeDetector(context, this,
+ SingleAxisSwipeDetector.VERTICAL);
mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@@ -97,7 +99,7 @@
}
int directionsToDetectScroll = mSwipeDetector.isIdleState() ?
- SwipeDetector.DIRECTION_NEGATIVE : 0;
+ SingleAxisSwipeDetector.DIRECTION_NEGATIVE : 0;
mSwipeDetector.setDetectableScrollConditions(
directionsToDetectScroll, false);
mSwipeDetector.onTouchEvent(ev);
@@ -122,7 +124,7 @@
return mIsOpen && mOpenCloseAnimator.isRunning();
}
- /* SwipeDetector.Listener */
+ /* SingleAxisSwipeDetector.Listener */
@Override
public void onDragStart(boolean start) { }
@@ -136,17 +138,17 @@
}
@Override
- public void onDragEnd(float velocity, boolean fling) {
- if ((fling && velocity > 0) || mTranslationShift > 0.5f) {
+ public void onDragEnd(float velocity) {
+ if ((mSwipeDetector.isFling(velocity) && velocity > 0) || mTranslationShift > 0.5f) {
mScrollInterpolator = scrollInterpolatorForVelocity(velocity);
- mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(
+ mOpenCloseAnimator.setDuration(BaseSwipeDetector.calculateDuration(
velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift));
close(true);
} else {
mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(
TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator.setDuration(
- SwipeDetector.calculateDuration(velocity, mTranslationShift))
+ BaseSwipeDetector.calculateDuration(velocity, mTranslationShift))
.setInterpolator(Interpolators.DEACCEL);
mOpenCloseAnimator.start();
}
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 2a4c5a7..e43fc8a 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -170,10 +170,8 @@
// Only look for controllers if we are not dispatching from gesture area and proxy is
// not active
mActiveController = findControllerToHandleTouch(ev);
-
- if (mActiveController != null) return true;
}
- return false;
+ return mActiveController != null;
}
@Override
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
index bd6ea50..23f21a1 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
@@ -10,7 +10,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationComponents;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
/**
@@ -21,7 +21,7 @@
private MotionEvent mTouchDownEvent;
public AllAppsSwipeController(Launcher l) {
- super(l, SwipeDetector.VERTICAL);
+ super(l, SingleAxisSwipeDetector.VERTICAL);
}
@Override
diff --git a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
similarity index 72%
rename from tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
rename to tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
index f209fae..5174e4d 100644
--- a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
@@ -15,6 +15,12 @@
*/
package com.android.launcher3.touch;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
+
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyFloat;
import static org.mockito.Matchers.anyObject;
@@ -39,19 +45,19 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class SwipeDetectorTest {
+public class SingleAxisSwipeDetectorTest {
- private static final String TAG = SwipeDetectorTest.class.getSimpleName();
+ private static final String TAG = SingleAxisSwipeDetectorTest.class.getSimpleName();
public static void L(String s, Object... parts) {
Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts));
}
private TouchEventGenerator mGenerator;
- private SwipeDetector mDetector;
+ private SingleAxisSwipeDetector mDetector;
private int mTouchSlop;
@Mock
- private SwipeDetector.Listener mMockListener;
+ private SingleAxisSwipeDetector.Listener mMockListener;
@Mock
private ViewConfiguration mMockConfig;
@@ -65,8 +71,8 @@
doReturn(orgConfig.getScaledMaximumFlingVelocity()).when(mMockConfig)
.getScaledMaximumFlingVelocity();
- mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL, false);
- mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
+ mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+ mDetector.setDetectableScrollConditions(DIRECTION_BOTH, false);
mTouchSlop = orgConfig.getScaledTouchSlop();
doReturn(mTouchSlop).when(mMockConfig).getScaledTouchSlop();
@@ -75,8 +81,8 @@
@Test
public void testDragStart_verticalPositive() {
- mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL, false);
- mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+ mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+ mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100, 100 - mTouchSlop);
// TODO: actually calculate the following parameters and do exact value checks.
@@ -85,8 +91,8 @@
@Test
public void testDragStart_verticalNegative() {
- mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL, false);
- mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_NEGATIVE, false);
+ mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+ mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100, 100 + mTouchSlop);
// TODO: actually calculate the following parameters and do exact value checks.
@@ -103,8 +109,8 @@
@Test
public void testDragStart_horizontalPositive() {
- mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, false);
- mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+ mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, false);
+ mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100 + mTouchSlop, 100);
@@ -114,8 +120,8 @@
@Test
public void testDragStart_horizontalNegative() {
- mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, false);
- mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_NEGATIVE, false);
+ mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, false);
+ mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100 - mTouchSlop, 100);
@@ -125,8 +131,8 @@
@Test
public void testDragStart_horizontalRtlPositive() {
- mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, true);
- mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+ mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, true);
+ mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100 - mTouchSlop, 100);
@@ -136,8 +142,8 @@
@Test
public void testDragStart_horizontalRtlNegative() {
- mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, true);
- mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_NEGATIVE, false);
+ mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, true);
+ mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100 + mTouchSlop, 100);
@@ -160,6 +166,6 @@
mGenerator.move(0, 100, 100 + mTouchSlop * 2);
mGenerator.lift(0);
// TODO: actually calculate the following parameters and do exact value checks.
- verify(mMockListener).onDragEnd(anyFloat(), anyBoolean());
+ verify(mMockListener).onDragEnd(anyFloat());
}
}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 709822b..2cf6c2b 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -342,22 +342,6 @@
}
}
- /**
- * Test dragging a custom shortcut to the workspace and launch it.
- *
- * A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it.
- * Custom shortcuts are replaced by deep shortcuts after api 25.
- */
- @Test
- @PortraitLandscape
- public void testDragCustomShortcut() {
- mLauncher.getWorkspace().openAllWidgets()
- .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
- .dragToWorkspace();
- mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut")
- .launch(getAppPackageName());
- }
-
public static String getAppPackageName() {
return getInstrumentation().getContext().getPackageName();
}
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index b8ca5de..f9d1d93 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -69,4 +69,22 @@
assertNotNull("Widget not found on the workspace", widget);
widget.launch(getAppPackageName());
}
+
+ /**
+ * Test dragging a custom shortcut to the workspace and launch it.
+ *
+ * A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it.
+ * Custom shortcuts are replaced by deep shortcuts after api 25.
+ */
+ @Test
+ @PortraitLandscape
+ public void testDragCustomShortcut() throws Throwable {
+ clearHomescreen();
+ mDevice.pressHome();
+ mLauncher.getWorkspace().openAllWidgets()
+ .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
+ .dragToWorkspace();
+ mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut")
+ .launch(getAppPackageName());
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 6dced8c..96e4b8c 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -36,6 +36,7 @@
private static final int MAX_SCROLL_ATTEMPTS = 40;
private final int mHeight;
+ private final int mIconHeight;
AllApps(LauncherInstrumentation launcher) {
super(launcher);
@@ -46,6 +47,10 @@
// Wait for the recycler to populate.
mLauncher.waitForObjectInContainer(appListRecycler, By.clazz(TextView.class));
verifyNotFrozen("All apps freeze flags upon opening all apps");
+ mIconHeight = mLauncher.getTestInfo(
+ TestProtocol.REQUEST_ICON_HEIGHT)
+ .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+
}
@Override
@@ -62,6 +67,10 @@
}
final Rect iconBounds = icon.getVisibleBounds();
LauncherInstrumentation.log("hasClickableIcon: icon bounds: " + iconBounds);
+ if (iconBounds.height() < mIconHeight / 2) {
+ LauncherInstrumentation.log("hasClickableIcon: icon has insufficient height");
+ return false;
+ }
if (iconCenterInSearchBox(allAppsContainer, icon)) {
LauncherInstrumentation.log("hasClickableIcon: icon center is under search box");
return false;