Changing the overviewState to show appsearch and floating header

Change-Id: I2cfd61cfc9978e4c8e4520f0f7217e49e7344c79
This commit is contained in:
Sunny Goyal
2018-03-19 20:02:34 -07:00
parent 9d69c8da9a
commit 7185dd63eb
35 changed files with 860 additions and 1292 deletions
@@ -85,8 +85,8 @@ public class AllAppsState extends LauncherState {
}
@Override
public float getHoseatAlpha(Launcher launcher) {
return 0;
public int getVisibleElements(Launcher launcher) {
return ALL_APPS_HEADER | ALL_APPS_CONTENT;
}
@Override
@@ -1,92 +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.uioverrides;
import com.android.launcher3.Alarm;
import com.android.launcher3.OnAlarmListener;
/**
* Utility class to detect a pause during a drag.
*/
public class DragPauseDetector implements OnAlarmListener {
private static final float MAX_VELOCITY_TO_PAUSE = 0.2f;
private static final long PAUSE_DURATION = 100;
private final Alarm mAlarm;
private final Runnable mOnPauseCallback;
private boolean mTriggered = false;
private int mDisabledFlags = 0;
public DragPauseDetector(Runnable onPauseCallback) {
mOnPauseCallback = onPauseCallback;
mAlarm = new Alarm();
mAlarm.setOnAlarmListener(this);
mAlarm.setAlarm(PAUSE_DURATION);
}
public void onDrag(float velocity) {
if (mTriggered || !isEnabled()) {
return;
}
if (Math.abs(velocity) > MAX_VELOCITY_TO_PAUSE) {
// Cancel any previous alarm and set a new alarm
mAlarm.setAlarm(PAUSE_DURATION);
}
}
@Override
public void onAlarm(Alarm alarm) {
if (!mTriggered && isEnabled()) {
mTriggered = true;
mOnPauseCallback.run();
}
}
public boolean isTriggered () {
return mTriggered;
}
public boolean isEnabled() {
return mDisabledFlags == 0;
}
public void addDisabledFlags(int flags) {
boolean wasEnabled = isEnabled();
mDisabledFlags |= flags;
resetAlarm(wasEnabled);
}
public void clearDisabledFlags(int flags) {
boolean wasEnabled = isEnabled();
mDisabledFlags &= ~flags;
resetAlarm(wasEnabled);
}
private void resetAlarm(boolean wasEnabled) {
boolean isEnabled = isEnabled();
if (wasEnabled == isEnabled) {
// Nothing has changed
} if (isEnabled && !mTriggered) {
mAlarm.setAlarm(PAUSE_DURATION);
} else if (!isEnabled) {
mAlarm.cancelAlarm();
}
}
}
@@ -1,165 +0,0 @@
/*
* 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.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.touch.SwipeDetector.DIRECTION_NEGATIVE;
import static com.android.launcher3.touch.SwipeDetector.DIRECTION_POSITIVE;
import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
import static com.android.launcher3.touch.SwipeDetector.VERTICAL;
import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
import android.graphics.Rect;
import android.metrics.LogMaker;
import android.view.MotionEvent;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.VerticalSwipeController;
import com.android.quickstep.views.RecentsView;
class EventLogTags {
private EventLogTags() {
} // don't instantiate
/** 524292 sysui_multi_action (content|4) */
public static final int SYSUI_MULTI_ACTION = 524292;
public static void writeSysuiMultiAction(Object[] content) {
android.util.EventLog.writeEvent(SYSUI_MULTI_ACTION, content);
}
}
class MetricsLogger {
private static MetricsLogger sMetricsLogger;
private static MetricsLogger getLogger() {
if (sMetricsLogger == null) {
sMetricsLogger = new MetricsLogger();
}
return sMetricsLogger;
}
protected void saveLog(Object[] rep) {
EventLogTags.writeSysuiMultiAction(rep);
}
public void write(LogMaker content) {
if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
content.setType(4/*MetricsEvent.TYPE_ACTION*/);
}
saveLog(content.serialize());
}
}
/**
* Extension of {@link VerticalSwipeController} to go from NORMAL to OVERVIEW.
*/
public class EdgeSwipeController extends VerticalSwipeController implements
OnDeviceProfileChangeListener {
private static final Rect sTempRect = new Rect();
private final MetricsLogger mMetricsLogger = new MetricsLogger();
public EdgeSwipeController(Launcher l) {
super(l, NORMAL, OVERVIEW, l.getDeviceProfile().isVerticalBarLayout()
? HORIZONTAL : VERTICAL);
l.addOnDeviceProfileChangeListener(this);
}
@Override
public void onDeviceProfileChanged(DeviceProfile dp) {
mDetector.updateDirection(dp.isVerticalBarLayout() ? HORIZONTAL : VERTICAL);
}
@Override
protected boolean shouldInterceptTouch(MotionEvent ev) {
return mLauncher.isInState(NORMAL) && (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
}
@Override
protected int getSwipeDirection(MotionEvent ev) {
return isTransitionFlipped() ? DIRECTION_NEGATIVE : DIRECTION_POSITIVE;
}
public EdgeSwipeController(Launcher l, LauncherState baseState) {
super(l, baseState);
}
@Override
protected boolean isTransitionFlipped() {
return mLauncher.getDeviceProfile().isSeascape();
}
@Override
protected void onTransitionComplete(boolean wasFling, boolean stateChanged) {
if (stateChanged && mToState instanceof OverviewState) {
// Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
// "Recents" activity for app transition tests.
final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
builder.setPackageName("com.android.systemui");
builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
"com.android.systemui.recents.RecentsActivity");
builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
0/* zero time */);
mMetricsLogger.write(builder);
// Add user event logging for launcher pipeline
int direction = Direction.UP;
if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
direction = Direction.LEFT;
if (mLauncher.getDeviceProfile().isSeascape()) {
direction = Direction.RIGHT;
}
}
mLauncher.getUserEventDispatcher().logStateChangeAction(
wasFling ? Touch.FLING : Touch.SWIPE, direction,
ContainerType.NAVBAR, ContainerType.WORKSPACE, // src target
ContainerType.TASKSWITCHER, // dst target
mLauncher.getWorkspace().getCurrentPage());
}
}
@Override
protected float getShiftRange() {
return getShiftRange(mLauncher);
}
public static float getShiftRange(Launcher launcher) {
RecentsView.getPageRect(launcher.getDeviceProfile(), launcher, sTempRect);
DragLayer dl = launcher.getDragLayer();
Rect insets = dl.getInsets();
DeviceProfile dp = launcher.getDeviceProfile();
if (dp.isVerticalBarLayout()) {
if (dp.isSeascape()) {
return insets.left + sTempRect.left;
} else {
return dl.getWidth() - sTempRect.right + insets.right;
}
} else {
return dl.getHeight() - sTempRect.bottom + insets.bottom;
}
}
}
@@ -41,10 +41,10 @@ public class FastOverviewState extends OverviewState {
}
@Override
public float getHoseatAlpha(Launcher launcher) {
public int getVisibleElements(Launcher launcher) {
if (DEBUG_DIFFERENT_UI) {
return 0;
return NONE;
}
return super.getHoseatAlpha(launcher);
return super.getVisibleElements(launcher);
}
}
@@ -0,0 +1,71 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.quickstep.util.SysuiEventLogger;
/**
* Touch controller for handling edge swipes in landscape/seascape UI
*/
public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchController {
public LandscapeEdgeSwipeController(Launcher l) {
super(l, SwipeDetector.HORIZONTAL);
}
@Override
protected boolean canInterceptTouch(MotionEvent ev) {
if (mCurrentAnimation != null) {
// If we are already animating from a previous state, we can intercept.
return true;
}
if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
return false;
}
return mLauncher.isInState(NORMAL) && (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
}
@Override
protected int getSwipeDirection(MotionEvent ev) {
mFromState = NORMAL;
mToState = OVERVIEW;
return SwipeDetector.DIRECTION_BOTH;
}
@Override
protected float getShiftRange() {
return mLauncher.getDragLayer().getWidth();
}
@Override
protected float initCurrentAnimation() {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
mCurrentAnimation = mLauncher.getStateManager()
.createAnimationToNewWorkspace(mToState, maxAccuracy);
return (mLauncher.getDeviceProfile().isSeascape() ? 2 : -2) / range;
}
@Override
protected int getDirectionForLog() {
return mLauncher.getDeviceProfile().isSeascape() ? Direction.RIGHT : Direction.LEFT;
}
@Override
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
super.onSwipeInteractionCompleted(targetState, logAction);
if (mFromState == NORMAL && targetState == OVERVIEW) {
SysuiEventLogger.writeDummyRecentsTransition(0);
}
}
}
@@ -0,0 +1,70 @@
/*
* 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.uioverrides;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.views.RecentsView;
/**
* Touch controller from going from OVERVIEW to ALL_APPS
*/
public class LandscapeStatesTouchController extends PortraitStatesTouchController {
public LandscapeStatesTouchController(Launcher l) {
super(l);
}
@Override
protected boolean canInterceptTouch(MotionEvent ev) {
if (mCurrentAnimation != null) {
// If we are already animating from a previous state, we can intercept.
return true;
}
if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
return false;
}
if (mLauncher.isInState(ALL_APPS)) {
// In all-apps only listen if the container cannot scroll itself
return mLauncher.getAppsView().shouldContainerScroll(ev);
} else if (mLauncher.isInState(NORMAL)) {
return true;
} else if (mLauncher.isInState(OVERVIEW)) {
RecentsView rv = mLauncher.getOverviewPanel();
return ev.getY() > (rv.getBottom() - rv.getPaddingBottom());
} else {
return false;
}
}
protected LauncherState getTargetState() {
if (mLauncher.isInState(ALL_APPS)) {
// Should swipe down go to OVERVIEW instead?
return TouchInteractionService.isConnected() ?
mLauncher.getStateManager().getLastState() : NORMAL;
} else {
return ALL_APPS;
}
}
}
@@ -22,6 +22,7 @@ import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
import android.graphics.Rect;
import android.view.View;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
@@ -105,4 +106,29 @@ public class OverviewState extends LauncherState {
return new float[] {scale, 0, translationY};
}
@Override
public int getVisibleElements(Launcher launcher) {
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
// TODO: Remove hotseat from overview
return HOTSEAT;
} else {
return launcher.getAppsView().getFloatingHeaderView().hasVisibleContent()
? ALL_APPS_HEADER : HOTSEAT;
}
}
@Override
public float getVerticalProgress(Launcher launcher) {
if (getVisibleElements(launcher) == HOTSEAT) {
return super.getVerticalProgress(launcher);
}
return 1 - (getDefaultSwipeHeight(launcher)
/ launcher.getAllAppsController().getShiftRange());
}
public static float getDefaultSwipeHeight(Launcher launcher) {
DeviceProfile dp = launcher.getDeviceProfile();
return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
}
}
@@ -1,71 +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.uioverrides;
import static com.android.launcher3.LauncherState.OVERVIEW;
import android.view.MotionEvent;
import com.android.launcher3.Launcher;
import com.android.launcher3.touch.SwipeDetector;
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.util.VerticalSwipeController;
/**
* Extension of {@link VerticalSwipeController} which allows swipe up from OVERVIEW to ALL_APPS
* Note that the swipe down is handled by {@link TwoStepSwipeController}.
*/
public class OverviewSwipeUpController extends VerticalSwipeController {
public OverviewSwipeUpController(Launcher l) {
super(l, OVERVIEW);
}
@Override
protected boolean shouldInterceptTouch(MotionEvent ev) {
if (!mLauncher.isInState(OVERVIEW)) {
return false;
}
if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
return ev.getY() >
mLauncher.getDragLayer().getHeight() * OVERVIEW.getVerticalProgress(mLauncher);
} else {
return mLauncher.getDragLayer().isEventOverHotseat(ev);
}
}
@Override
protected int getSwipeDirection(MotionEvent ev) {
return SwipeDetector.DIRECTION_POSITIVE;
}
@Override
protected void onTransitionComplete(boolean wasFling, boolean stateChanged) {
if (stateChanged) {
// Transition complete. log the action
mLauncher.getUserEventDispatcher().logStateChangeAction(
wasFling ? Touch.FLING : Touch.SWIPE,
Direction.UP,
ContainerType.HOTSEAT,
ContainerType.TASKSWITCHER,
ContainerType.ALLAPPS,
mLauncher.getWorkspace().getCurrentPage());
}
}
}
@@ -0,0 +1,128 @@
/*
* 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.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.SysuiEventLogger;
/**
* Touch controller for handling various state transitions in portrait UI.
*/
public class PortraitStatesTouchController extends AbstractStateChangeTouchController {
public PortraitStatesTouchController(Launcher l) {
super(l, SwipeDetector.VERTICAL);
}
@Override
protected boolean canInterceptTouch(MotionEvent ev) {
if (mCurrentAnimation != null) {
// If we are already animating from a previous state, we can intercept.
return true;
}
if (mLauncher.isInState(ALL_APPS)) {
// In all-apps only listen if the container cannot scroll itself
if (!mLauncher.getAppsView().shouldContainerScroll(ev)) {
return false;
}
} else {
// For all other states, only listen if the event originated below the hotseat height
DeviceProfile dp = mLauncher.getDeviceProfile();
int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
if (ev.getY() < (mLauncher.getDragLayer().getHeight() - hotseatHeight)) {
return false;
}
}
if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
return false;
}
return true;
}
@Override
protected int getSwipeDirection(MotionEvent ev) {
final int directionsToDetectScroll;
if (mLauncher.isInState(ALL_APPS)) {
directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
mStartContainerType = ContainerType.ALLAPPS;
} else if (mLauncher.isInState(NORMAL)) {
directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
mStartContainerType = ContainerType.HOTSEAT;
} else if (mLauncher.isInState(OVERVIEW)) {
directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
mStartContainerType = ContainerType.TASKSWITCHER;
} else {
return 0;
}
mFromState = mLauncher.getStateManager().getState();
mToState = getTargetState();
if (mFromState == mToState) {
return 0;
}
return directionsToDetectScroll;
}
protected LauncherState getTargetState() {
if (mLauncher.isInState(ALL_APPS)) {
// Should swipe down go to OVERVIEW instead?
return TouchInteractionService.isConnected() ?
mLauncher.getStateManager().getLastState() : NORMAL;
} else if (mLauncher.isInState(OVERVIEW)) {
return ALL_APPS;
} else {
return TouchInteractionService.isConnected() ? OVERVIEW : ALL_APPS;
}
}
@Override
protected float initCurrentAnimation() {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
mCurrentAnimation = mLauncher.getStateManager()
.createAnimationToNewWorkspace(mToState, maxAccuracy);
float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range;
float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range;
float totalShift = endVerticalShift - startVerticalShift;
if (totalShift == 0) {
totalShift = Math.signum(mFromState.ordinal - mToState.ordinal)
* OverviewState.getDefaultSwipeHeight(mLauncher);
}
return 1 / totalShift;
}
@Override
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
super.onSwipeInteractionCompleted(targetState, logAction);
if (mFromState == NORMAL && targetState == OVERVIEW) {
SysuiEventLogger.writeDummyRecentsTransition(0);
}
}
}
@@ -1,51 +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.uioverrides;
import android.animation.Animator;
import android.util.SparseArray;
import com.android.launcher3.anim.AnimatorSetBuilder;
import java.util.Collections;
import java.util.List;
public class TaggedAnimatorSetBuilder extends AnimatorSetBuilder {
/**
* Map of the index in {@link #mAnims} to tag. All the animations in {@link #mAnims} starting
* from this index correspond to the tag (until a new tag is specified for an index)
*/
private final SparseArray<Object> mTags = new SparseArray<>();
@Override
public void startTag(Object obj) {
mTags.put(mAnims.size(), obj);
}
public List<Animator> getAnimationsForTag(Object tag) {
int startIndex = mTags.indexOfValue(tag);
if (startIndex < 0) {
return Collections.emptyList();
}
int startPos = mTags.keyAt(startIndex);
int endIndex = startIndex + 1;
int endPos = endIndex >= mTags.size() ? mAnims.size() : mTags.keyAt(endIndex);
return mAnims.subList(startPos, endPos);
}
}
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
@@ -30,24 +28,21 @@ import android.view.View;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.touch.SwipeDetector;
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.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.PendingAnimation;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
/**
* Touch controller for swipe interaction in Overview state
* Touch controller for handling task view card swipes
*/
public class OverviewSwipeController extends AnimatorListenerAdapter
public class TaskViewTouchController extends AnimatorListenerAdapter
implements TouchController, SwipeDetector.Listener {
private static final String TAG = "OverviewSwipeController";
@@ -68,16 +63,14 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
private boolean mCurrentAnimationIsGoingUp;
private boolean mNoIntercept;
private boolean mSwipeDownEnabled;
private float mDisplacementShift;
private float mProgressMultiplier;
private float mEndDisplacement;
private int mStartingTarget;
private TaskView mTaskBeingDragged;
public OverviewSwipeController(Launcher launcher) {
public TaskViewTouchController(Launcher launcher) {
mLauncher = launcher;
mRecentsView = launcher.getOverviewPanel();
mDetector = new SwipeDetector(launcher, this, SwipeDetector.VERTICAL);
@@ -94,15 +87,6 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
return mLauncher.isInState(OVERVIEW);
}
private boolean isEventOverHotseat(MotionEvent ev) {
if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
return ev.getY() >
mLauncher.getDragLayer().getHeight() * OVERVIEW.getVerticalProgress(mLauncher);
} else {
return mLauncher.getDragLayer().isEventOverHotseat(ev);
}
}
@Override
public void onAnimationCancel(Animator animation) {
if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
@@ -129,22 +113,14 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
ignoreSlopWhenSettling = true;
} else {
mTaskBeingDragged = null;
mSwipeDownEnabled = true;
View view = mRecentsView.getChildAt(mRecentsView.getCurrentPage());
if (view instanceof TaskView && mLauncher.getDragLayer().isEventOverView(view, ev)) {
// The tile can be dragged down to open the task.
mTaskBeingDragged = (TaskView) view;
directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
mStartingTarget = LauncherLogProto.ItemType.TASK;
} else if (isEventOverHotseat(ev)) {
// The hotseat is being dragged
directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
mSwipeDownEnabled = false;
mStartingTarget = ContainerType.HOTSEAT;
} else {
mNoIntercept = true;
mStartingTarget = ContainerType.WORKSPACE;
return false;
}
}
@@ -167,9 +143,6 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
}
private void reInitAnimationController(boolean goingUp) {
if (!goingUp && !mSwipeDownEnabled) {
goingUp = true;
}
if (mCurrentAnimation != null && mCurrentAnimationIsGoingUp == goingUp) {
// No need to init
return;
@@ -187,31 +160,20 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
long maxDuration = (long) (2 * range);
DragLayer dl = mLauncher.getDragLayer();
if (mTaskBeingDragged == null) {
// User is either going to all apps or home
mCurrentAnimation = mLauncher.getStateManager()
.createAnimationToNewWorkspace(goingUp ? ALL_APPS : NORMAL, maxDuration);
if (goingUp) {
mEndDisplacement = -range;
} else {
mEndDisplacement = EdgeSwipeController.getShiftRange(mLauncher);
}
if (goingUp) {
mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
true /* animateTaskView */, true /* removeTask */, maxDuration);
mCurrentAnimation = AnimatorPlaybackController
.wrap(mPendingAnimation.anim, maxDuration);
mEndDisplacement = -mTaskBeingDragged.getHeight();
} else {
if (goingUp) {
mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
true /* animateTaskView */, true /* removeTask */, maxDuration);
mCurrentAnimation = AnimatorPlaybackController
.wrap(mPendingAnimation.anim, maxDuration);
mEndDisplacement = -mTaskBeingDragged.getHeight();
} else {
AnimatorSet anim = new AnimatorSet();
// TODO: Setup a zoom animation
mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
AnimatorSet anim = new AnimatorSet();
// TODO: Setup a zoom animation
mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
mTempCords[1] = mTaskBeingDragged.getHeight();
dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
mEndDisplacement = dl.getHeight() - mTempCords[1];
}
mTempCords[1] = mTaskBeingDragged.getHeight();
dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
mEndDisplacement = dl.getHeight() - mTempCords[1];
}
mCurrentAnimation.getTarget().addListener(this);
@@ -249,9 +211,7 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
if (fling) {
logAction = Touch.FLING;
boolean goingUp = velocity < 0;
if (!goingUp && !mSwipeDownEnabled) {
goingToEnd = false;
} else if (goingUp != mCurrentAnimationIsGoingUp) {
if (goingUp != mCurrentAnimationIsGoingUp) {
// In case the fling is in opposite direction, make sure if is close enough
// from the start position
if (mCurrentAnimation.getProgressFraction()
@@ -277,7 +237,6 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
float nextFrameProgress = Utilities.boundToRange(
progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f);
mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction));
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
@@ -292,25 +251,13 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
mPendingAnimation.finish(wasSuccess);
mPendingAnimation = null;
}
if (mTaskBeingDragged == null) {
LauncherState state = wasSuccess ?
(mCurrentAnimationIsGoingUp ? ALL_APPS : NORMAL) : OVERVIEW;
mLauncher.getStateManager().goToState(state, false);
} else if (wasSuccess) {
if (wasSuccess) {
if (!mCurrentAnimationIsGoingUp) {
mTaskBeingDragged.launchTask(false);
mLauncher.getUserEventDispatcher().logTaskLaunch(logAction,
Direction.DOWN, mTaskBeingDragged.getTask().getTopComponent());
}
}
if (mTaskBeingDragged == null || (wasSuccess && mCurrentAnimationIsGoingUp)) {
mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
mCurrentAnimationIsGoingUp ? Direction.UP : Direction.DOWN,
mStartingTarget, ContainerType.TASKSWITCHER,
mLauncher.getStateManager().getState().containerType,
mRecentsView.getCurrentPage());
}
mDetector.finishedScrolling();
mTaskBeingDragged = null;
mCurrentAnimation = null;
@@ -1,447 +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.uioverrides;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.util.Log;
import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
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.LauncherStateManager.StateHandler;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.touch.SwipeDetector;
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.util.FloatRange;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.TouchInteractionService;
/**
* Handles vertical touch gesture on the DragLayer
*/
public class TwoStepSwipeController extends AnimatorListenerAdapter
implements TouchController, SwipeDetector.Listener {
private static final String TAG = "TwoStepSwipeController";
private static final float RECATCH_REJECTION_FRACTION = .0875f;
private static final int SINGLE_FRAME_MS = 16;
private static final long QUICK_SNAP_TO_OVERVIEW_DURATION = 250;
// Progress after which the transition is assumed to be a success in case user does not fling
private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
/**
* Index of the vertical swipe handles in {@link LauncherStateManager#getStateHandlers()}.
*/
private static final int SWIPE_HANDLER_INDEX = 0;
/**
* Index of various UI handlers in {@link LauncherStateManager#getStateHandlers()} not related
* to vertical swipe.
*/
private static final int OTHER_HANDLERS_START_INDEX = SWIPE_HANDLER_INDEX + 1;
// Swipe progress range (when starting from NORMAL state) where OVERVIEW state is allowed
private static final float MIN_PROGRESS_TO_OVERVIEW = 0.1f;
private static final float MAX_PROGRESS_TO_OVERVIEW = 0.4f;
private static final int FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE = 1 << 0;
private static final int FLAG_OVERVIEW_DISABLED_FLING = 1 << 1;
private static final int FLAG_OVERVIEW_DISABLED_CANCEL_STATE = 1 << 2;
private static final int FLAG_OVERVIEW_DISABLED = 1 << 4;
private static final int FLAG_DISABLED_TWO_TARGETS = 1 << 5;
private static final int FLAG_DISABLED_BACK_TARGET = 1 << 6;
private final Launcher mLauncher;
private final SwipeDetector mDetector;
private boolean mNoIntercept;
private int mStartContainerType;
private DragPauseDetector mDragPauseDetector;
private FloatRange mOverviewProgressRange;
private TaggedAnimatorSetBuilder mTaggedAnimatorSetBuilder;
private AnimatorSet mQuickOverviewAnimation;
private boolean mAnimatingToOverview;
private CroppedAnimationController mCroppedAnimationController;
private AnimatorPlaybackController mCurrentAnimation;
private LauncherState mFromState;
private LauncherState mToState;
private float mStartProgress;
// Ratio of transition process [0, 1] to drag displacement (px)
private float mProgressMultiplier;
public TwoStepSwipeController(Launcher l) {
mLauncher = l;
mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
}
private boolean canInterceptTouch(MotionEvent ev) {
if (mCurrentAnimation != null) {
// If we are already animating from a previous state, we can intercept.
return true;
}
if (mLauncher.isInState(NORMAL)) {
if ((ev.getEdgeFlags() & EDGE_NAV_BAR) != 0 &&
!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
// On normal swipes ignore edge swipes
return false;
}
} else if (mLauncher.isInState(ALL_APPS)) {
if (!mLauncher.getAppsView().shouldContainerScroll(ev)) {
return false;
}
} else {
// Don't listen for the swipe gesture if we are already in some other state.
return false;
}
if (mAnimatingToOverview) {
return false;
}
if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
return false;
}
return true;
}
@Override
public void onAnimationCancel(Animator animation) {
if (mCurrentAnimation != null && animation == mCurrentAnimation.getOriginalTarget()) {
Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
clearState();
}
}
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = !canInterceptTouch(ev);
if (mNoIntercept) {
return false;
}
// Now figure out which direction scroll events the controller will start
// calling the callbacks.
final int directionsToDetectScroll;
boolean ignoreSlopWhenSettling = false;
if (mCurrentAnimation != null) {
if (mCurrentAnimation.getProgressFraction() > 1 - RECATCH_REJECTION_FRACTION) {
directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
} else if (mCurrentAnimation.getProgressFraction() < RECATCH_REJECTION_FRACTION ) {
directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
} else {
directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
ignoreSlopWhenSettling = true;
}
} else {
if (mLauncher.isInState(ALL_APPS)) {
directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
mStartContainerType = ContainerType.ALLAPPS;
} else {
directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
mStartContainerType = mLauncher.getDragLayer().isEventOverHotseat(ev) ?
ContainerType.HOTSEAT : ContainerType.WORKSPACE;
}
}
mDetector.setDetectableScrollConditions(
directionsToDetectScroll, ignoreSlopWhenSettling);
}
if (mNoIntercept) {
return false;
}
onControllerTouchEvent(ev);
return mDetector.isDraggingOrSettling();
}
@Override
public boolean onControllerTouchEvent(MotionEvent ev) {
return mDetector.onTouchEvent(ev);
}
@Override
public void onDragStart(boolean start) {
if (mCurrentAnimation == null) {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
mDragPauseDetector = new DragPauseDetector(this::onDragPauseDetected);
mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE);
if (FeatureFlags.ENABLE_TWO_SWIPE_TARGETS) {
mDragPauseDetector.addDisabledFlags(FLAG_DISABLED_TWO_TARGETS);
}
mOverviewProgressRange = new FloatRange();
mOverviewProgressRange.start = mLauncher.isInState(NORMAL)
? MIN_PROGRESS_TO_OVERVIEW
: 1 - MAX_PROGRESS_TO_OVERVIEW;
mOverviewProgressRange.end = mOverviewProgressRange.start
+ MAX_PROGRESS_TO_OVERVIEW - MIN_PROGRESS_TO_OVERVIEW;
// Build current animation
mFromState = mLauncher.getStateManager().getState();
mToState = mLauncher.isInState(ALL_APPS) ? NORMAL : ALL_APPS;
if (mToState == NORMAL && mLauncher.getStateManager().getLastState() == OVERVIEW) {
mToState = OVERVIEW;
mDragPauseDetector.addDisabledFlags(FLAG_DISABLED_BACK_TARGET);
}
mTaggedAnimatorSetBuilder = new TaggedAnimatorSetBuilder();
mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(
mToState, mTaggedAnimatorSetBuilder, maxAccuracy);
if (!TouchInteractionService.isConnected()) {
mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED);
}
mCurrentAnimation.getTarget().addListener(this);
mStartProgress = 0;
mProgressMultiplier = (mLauncher.isInState(ALL_APPS) ? 1 : -1) / range;
mCurrentAnimation.dispatchOnStart();
} else {
mCurrentAnimation.pause();
mStartProgress = mCurrentAnimation.getProgressFraction();
mDragPauseDetector.clearDisabledFlags(FLAG_OVERVIEW_DISABLED_FLING);
updatePauseDetectorRangeFlag();
}
}
private float getShiftRange() {
return mLauncher.getAllAppsController().getShiftRange();
}
@Override
public boolean onDrag(float displacement, float velocity) {
float deltaProgress = mProgressMultiplier * displacement;
mCurrentAnimation.setPlayFraction(deltaProgress + mStartProgress);
updatePauseDetectorRangeFlag();
mDragPauseDetector.onDrag(velocity);
return true;
}
private void updatePauseDetectorRangeFlag() {
if (mOverviewProgressRange.contains(mCurrentAnimation.getProgressFraction())) {
mDragPauseDetector.clearDisabledFlags(FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE);
} else {
mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE);
}
}
@Override
public void onDragEnd(float velocity, boolean fling) {
mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_FLING);
final int logAction;
LauncherState targetState;
final float progress = mCurrentAnimation.getProgressFraction();
if (fling) {
logAction = Touch.FLING;
targetState = velocity < 0 ? ALL_APPS : mLauncher.getStateManager().getLastState();
// snap to top or bottom using the release velocity
} else {
logAction = Touch.SWIPE;
targetState = (progress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
}
float endProgress;
if (mDragPauseDetector.isTriggered() && targetState == NORMAL) {
targetState = OVERVIEW;
endProgress = OVERVIEW.getVerticalProgress(mLauncher);
if (mFromState == NORMAL) {
endProgress = 1 - endProgress;
}
} else if (targetState == mToState) {
endProgress = 1;
} else {
endProgress = 0;
}
LauncherState targetStateFinal = targetState;
mCurrentAnimation.setEndAction(() ->
onSwipeInteractionCompleted(targetStateFinal, logAction));
float nextFrameProgress = Utilities.boundToRange(
progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
anim.setFloatValues(nextFrameProgress, endProgress);
anim.setDuration(
SwipeDetector.calculateDuration(velocity, Math.abs(endProgress - progress)));
anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
anim.start();
}
private void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
if (targetState != mFromState) {
// Transition complete. log the action
mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
mToState == ALL_APPS ? Direction.UP : Direction.DOWN,
mStartContainerType,
mFromState.containerType,
mToState.containerType,
mLauncher.getWorkspace().getCurrentPage());
}
clearState();
// TODO: mQuickOverviewAnimation might still be running in which changing a state instantly
// may cause a jump. Animate the state change with a short duration in this case?
mLauncher.getStateManager().goToState(targetState, false /* animated */);
}
private void onDragPauseDetected() {
final ValueAnimator twoStepAnimator = ValueAnimator.ofFloat(0, 1);
twoStepAnimator.setDuration(mCurrentAnimation.getDuration());
StateHandler[] handlers = mLauncher.getStateManager().getStateHandlers();
// Change the current animation to only play the vertical handle
AnimatorSet anim = new AnimatorSet();
anim.playTogether(mTaggedAnimatorSetBuilder.getAnimationsForTag(
handlers[SWIPE_HANDLER_INDEX]));
anim.play(twoStepAnimator);
mCurrentAnimation = mCurrentAnimation.cloneFor(anim);
AnimatorSetBuilder builder = new AnimatorSetBuilder();
AnimationConfig config = new AnimationConfig();
config.duration = QUICK_SNAP_TO_OVERVIEW_DURATION;
for (int i = OTHER_HANDLERS_START_INDEX; i < handlers.length; i++) {
handlers[i].setStateWithAnimation(OVERVIEW, builder, config);
}
mQuickOverviewAnimation = builder.build();
mQuickOverviewAnimation.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
onQuickOverviewAnimationComplete(twoStepAnimator);
}
});
mQuickOverviewAnimation.start();
}
private void onQuickOverviewAnimationComplete(ValueAnimator animator) {
if (mAnimatingToOverview) {
return;
}
// For the remainder to the interaction, the user can either go to the ALL_APPS state or
// the OVERVIEW state.
// The remaining state handlers are on the OVERVIEW state. Create one animation towards the
// ALL_APPS state and only call it when the user moved above the current range.
AnimationConfig config = new AnimationConfig();
config.duration = (long) (2 * getShiftRange());
config.userControlled = true;
AnimatorSetBuilder builderToAllAppsState = new AnimatorSetBuilder();
StateHandler[] handlers = mLauncher.getStateManager().getStateHandlers();
for (int i = OTHER_HANDLERS_START_INDEX; i < handlers.length; i++) {
handlers[i].setStateWithAnimation(ALL_APPS, builderToAllAppsState, config);
}
mCroppedAnimationController = new CroppedAnimationController(
AnimatorPlaybackController.wrap(builderToAllAppsState.build(), config.duration),
new FloatRange(animator.getAnimatedFraction(), mToState == ALL_APPS ? 1 : 0));
animator.addUpdateListener(mCroppedAnimationController);
}
private void clearState() {
mCurrentAnimation = null;
mTaggedAnimatorSetBuilder = null;
if (mDragPauseDetector != null) {
mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_CANCEL_STATE);
}
mDragPauseDetector = null;
if (mQuickOverviewAnimation != null) {
mQuickOverviewAnimation.cancel();
mQuickOverviewAnimation = null;
}
mCroppedAnimationController = null;
mAnimatingToOverview = false;
mDetector.finishedScrolling();
}
/**
* {@link AnimatorUpdateListener} which controls another animation for a fraction of range
*/
private static class CroppedAnimationController implements AnimatorUpdateListener {
private final AnimatorPlaybackController mTarget;
private final FloatRange mRange;
CroppedAnimationController(AnimatorPlaybackController target, FloatRange range) {
mTarget = target;
mRange = range;
}
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float fraction = valueAnimator.getAnimatedFraction();
if (mRange.start < mRange.end) {
if (fraction <= mRange.start) {
mTarget.setPlayFraction(0);
} else if (fraction >= mRange.end) {
mTarget.setPlayFraction(1);
} else {
mTarget.setPlayFraction((fraction - mRange.start) / (mRange.end - mRange.start));
}
} else if (mRange.start > mRange.end) {
if (fraction >= mRange.start) {
mTarget.setPlayFraction(0);
} else if (fraction <= mRange.end) {
mTarget.setPlayFraction(1);
} else {
mTarget.setPlayFraction((fraction - mRange.start) / (mRange.end - mRange.start));
}
} else {
// mRange.start == mRange.end
mTarget.setPlayFraction(0);
}
}
}
}
@@ -25,7 +25,6 @@ import android.view.View.AccessibilityDelegate;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.OverviewInteractionState;
@@ -35,17 +34,17 @@ import com.android.quickstep.views.RecentsView;
public class UiFactory {
public static TouchController[] createTouchControllers(Launcher launcher) {
if (FeatureFlags.ENABLE_TWO_SWIPE_TARGETS) {
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
return new TouchController[] {
launcher.getDragController(),
new EdgeSwipeController(launcher),
new TwoStepSwipeController(launcher),
new OverviewSwipeController(launcher)};
new LandscapeStatesTouchController(launcher),
new LandscapeEdgeSwipeController(launcher),
new TaskViewTouchController(launcher)};
} else {
return new TouchController[] {
launcher.getDragController(),
new TwoStepSwipeController(launcher),
new OverviewSwipeController(launcher)};
new PortraitStatesTouchController(launcher),
new TaskViewTouchController(launcher)};
}
}
@@ -143,10 +143,13 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
activity.getStateManager().setRestState(startState);
if (!activityVisible) {
// Since the launcher is not visible, we can safely reset the scroll position.
// This ensures then the next swipe up to all-apps starts from scroll 0.
activity.getAppsView().reset(false /* animate */);
activity.getStateManager().goToState(OVERVIEW, false);
// Optimization, hide the all apps view to prevent layout while initializing
activity.getAppsView().setVisibility(View.GONE);
activity.getAppsView().getContentView().setVisibility(View.GONE);
}
}
@@ -160,20 +163,21 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
@Override
public AnimatorPlaybackController createControllerForHiddenActivity(
Launcher activity, int transitionLength) {
float startProgress;
AllAppsTransitionController controller = activity.getAllAppsController();
AnimatorSet anim = new AnimatorSet();
if (activity.getDeviceProfile().isVerticalBarLayout()) {
startProgress = 1;
// TODO:
} else {
float scrollRange = Math.max(controller.getShiftRange(), 1);
startProgress = (transitionLength / scrollRange) + 1;
float progressDelta = (transitionLength / scrollRange);
float endProgress = OVERVIEW.getVerticalProgress(activity);
float startProgress = endProgress + progressDelta;
ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(
controller, ALL_APPS_PROGRESS, startProgress, endProgress);
shiftAnim.setInterpolator(LINEAR);
anim.play(shiftAnim);
}
AnimatorSet anim = new AnimatorSet();
ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(controller, ALL_APPS_PROGRESS,
startProgress, OVERVIEW.getVerticalProgress(activity));
shiftAnim.setInterpolator(LINEAR);
anim.play(shiftAnim);
// TODO: Link this animation to state animation, so that it is cancelled
// automatically on state change
@@ -35,7 +35,6 @@ import android.graphics.Matrix.ScaleToFit;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.metrics.LogMaker;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -64,6 +63,7 @@ import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
import com.android.quickstep.ActivityControlHelper.LayoutListener;
import com.android.quickstep.TouchConsumer.InteractionType;
import com.android.quickstep.util.SysuiEventLogger;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -77,40 +77,6 @@ import com.android.systemui.shared.system.WindowManagerWrapper;
import java.util.StringJoiner;
class EventLogTags {
private EventLogTags() {
} // don't instantiate
/** 524292 sysui_multi_action (content|4) */
public static final int SYSUI_MULTI_ACTION = 524292;
public static void writeSysuiMultiAction(Object[] content) {
android.util.EventLog.writeEvent(SYSUI_MULTI_ACTION, content);
}
}
class MetricsLogger {
private static MetricsLogger sMetricsLogger;
private static MetricsLogger getLogger() {
if (sMetricsLogger == null) {
sMetricsLogger = new MetricsLogger();
}
return sMetricsLogger;
}
protected void saveLog(Object[] rep) {
EventLogTags.writeSysuiMultiAction(rep);
}
public void write(LogMaker content) {
if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
content.setType(4/*MetricsEvent.TYPE_ACTION*/);
}
saveLog(content.serialize());
}
}
@TargetApi(Build.VERSION_CODES.O)
public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
@@ -229,7 +195,6 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
private Matrix mTmpMatrix = new Matrix();
private final long mTouchTimeMs;
private long mLauncherFrameDrawnTime;
private final MetricsLogger mMetricsLogger = new MetricsLogger();
WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs,
ActivityControlHelper<T> controller) {
@@ -453,15 +418,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
onLauncherLayoutChanged();
final long transitionDelay = mLauncherFrameDrawnTime - mTouchTimeMs;
// Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
// "Recents" activity for app transition tests for the app-to-recents case.
final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
builder.setPackageName("com.android.systemui");
builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
"com.android.systemui.recents.RecentsActivity");
builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
transitionDelay);
mMetricsLogger.write(builder);
SysuiEventLogger.writeDummyRecentsTransition(transitionDelay);
if (LatencyTrackerCompat.isEnabled(mContext)) {
LatencyTrackerCompat.logToggleRecents((int) transitionDelay);
}
@@ -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.util;
import android.metrics.LogMaker;
import android.util.EventLog;
/**
* Utility class for writing logs on behalf of systemUI
*/
public class SysuiEventLogger {
/** 524292 sysui_multi_action (content|4) */
public static final int SYSUI_MULTI_ACTION = 524292;
private static void write(LogMaker content) {
if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
content.setType(4/*MetricsEvent.TYPE_ACTION*/);
}
EventLog.writeEvent(SYSUI_MULTI_ACTION, content.serialize());
}
public static void writeDummyRecentsTransition(long transitionDelay) {
// Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
// "Recents" activity for app transition tests for the app-to-recents case.
final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
builder.setPackageName("com.android.systemui");
builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
"com.android.systemui.recents.RecentsActivity");
builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
transitionDelay);
write(builder);
}
}