Merging ub-launcher3-qt-qpr1-dev, build 5872416

Test: Manual

Bug:121280703 P2 Finish implementation of PortraitLandscape annotation for tests
Bug:135633159 P3 [QPR] Promise icons on home screen
Bug:135638690 P1 [QPR] On-device app search
Bug:137106918 P2 UX: Accidental NGA triggering when hitting Undo actions in apps
Bug:137200188 P3 Home screen app title disappears during animation
Bug:138195597 P2 Wrong icon animated on return to home screen
Bug:138396187 P1 Swipe and hold from an app no longer uses spring to animate adjacent task
Bug:138729157 P1 [Flaky test] java.lang.AssertionError: http://go/tapl : want to fling backwards in widgets, flung back, but the current state is not WIDGETS; Unexpected launcher object visible: workspace
Bug:138729456 P1 [Flaky test] java.lang.AssertionError: http://go/tapl : Can't find an object with selector: BySelector [CLASS='\Qandroid.widget.TextView\E', PKG='\Qcom.google.android.apps.nexuslauncher\E', TEXT='\QShortcut 3\E'] (visible state: Workspace)
Bug:138964490 P1 Support DeviceConfig to drive ToggleableFlags
Bug:139021165 P2 [TEST TRACKER] [QPR] Promise icons on home screen
Bug:139137636 P2 Create memory tests for Launcher
Bug:139551306 P4 [Polish] Reduce shelf paddings in Overview
Bug:139885365 P3 App open animation different between launcher suggested apps and launcher home screen/drawer
Bug:139918680 P2 [a11y] Talkback shouldn't keep focusing on the background item and speak the description of the background item again after entering Widget list.
Bug:140076379 P1 Launcher ub-launcher3-qt-qpr1-dev Branch Build Keeps Crashing due to Exception
Bug:140252951 P2 Add widget launch test.
Bug:140308849 P2 Jank during swipe up to home, especially noticeable after pausing first
Bug:140311911 P2 Flake in Launcher tests: java.lang.AssertionError: Stable state != state: OverviewState, LauncherState
Bug:140405990 P2 [a11y] Unable to add shortcut to Home screen by Voice access or Switch access.
Bug:140819614 P1 If an install session is abandoned for an already installed app, the corresponding icon is removed
Bug:140823188 P1 AppPredictionsUITests failing
Bug:140837771 P1 Failing test: AddConfigWidgetTests and AddWidgetTests are failing
Change-Id: I1efae6216c53b1fee3e105c9356ed43c4bf46c6e
This commit is contained in:
Hyunyoung Song
2019-09-12 15:22:35 -07:00
54 changed files with 873 additions and 300 deletions
+3
View File
@@ -10,3 +10,6 @@ mrcasey@google.com
sunnygoyal@google.com
twickham@google.com
winsonc@google.com
per-file FeatureFlags.java = sunnygoyal@google.com, adamcohen@google.com
per-file BaseFlags.java = sunnygoyal@google.com, adamcohen@google.com
@@ -32,6 +32,7 @@ import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
import android.content.Context;
import android.view.View;
import com.android.launcher3.DeviceProfile;
@@ -115,10 +116,10 @@ public class OverviewState extends LauncherState {
}
public static float getDefaultSwipeHeight(Launcher launcher) {
return getDefaultSwipeHeight(launcher.getDeviceProfile());
return getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
}
public static float getDefaultSwipeHeight(DeviceProfile dp) {
public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
}
@@ -18,16 +18,11 @@ package com.android.launcher3;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS;
import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -35,18 +30,17 @@ import android.animation.ObjectAnimator;
import android.content.Context;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.SpringObjectAnimator;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* A {@link QuickstepAppTransitionManagerImpl} that also implements recents transitions from
* {@link RecentsView}.
@@ -156,8 +150,11 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti
return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
RecentsView.CONTENT_ALPHA, values);
case INDEX_RECENTS_TRANSLATE_X_ANIM:
return new SpringObjectAnimator<>(mLauncher.getOverviewPanel(),
VIEW_TRANSLATE_X, MIN_VISIBLE_CHANGE_PIXELS, 0.8f, 250, values);
return new SpringAnimationBuilder<>(mLauncher.getOverviewPanel(), VIEW_TRANSLATE_X)
.setDampingRatio(0.8f)
.setStiffness(250)
.setValues(values)
.build(mLauncher);
default:
return super.createStateElementAnimation(index, values);
}
@@ -24,7 +24,6 @@ import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.content.ComponentName;
import android.content.Context;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import com.android.launcher3.AppInfo;
import com.android.launcher3.InvariantDeviceProfile;
@@ -32,6 +31,8 @@ import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.StateListener;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
@@ -58,7 +59,7 @@ import java.util.List;
* 4) Maintains the current active client id (for the predictions) and all updates are performed on
* that client id.
*/
public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInfoUpdateReceiver,
public class PredictionUiStateManager implements StateListener, ItemInfoUpdateReceiver,
OnIDPChangeListener, OnUpdateListener {
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
@@ -153,7 +154,10 @@ public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInf
public void reapplyItemInfo(ItemInfoWithIcon info) { }
@Override
public void onGlobalLayout() {
public void onStateTransitionStart(LauncherState toState) { }
@Override
public void onStateTransitionComplete(LauncherState state) {
if (mAppsView == null) {
return;
}
@@ -162,7 +166,8 @@ public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInf
mPendingState = null;
}
if (mPendingState == null) {
mAppsView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Launcher.getLauncher(mAppsView.getContext()).getStateManager()
.removeStateListener(this);
}
}
@@ -170,9 +175,8 @@ public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInf
boolean registerListener = mPendingState == null;
mPendingState = state;
if (registerListener) {
// OnGlobalLayoutListener is called whenever a view in the view tree changes
// visibility. Add a listener and wait until appsView is invisible again.
mAppsView.getViewTreeObserver().addOnGlobalLayoutListener(this);
// Add a listener and wait until appsView is invisible again.
Launcher.getLauncher(mAppsView.getContext()).getStateManager().addStateListener(this);
}
}
@@ -32,6 +32,7 @@ import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
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;
@@ -159,11 +160,15 @@ public class OverviewState extends LauncherState {
}
public static float getDefaultSwipeHeight(Launcher launcher) {
return getDefaultSwipeHeight(launcher.getDeviceProfile());
return getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
}
public static float getDefaultSwipeHeight(DeviceProfile dp) {
return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
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;
}
@Override
@@ -40,7 +40,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.os.UserHandle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
@@ -58,7 +57,6 @@ import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.views.FloatingIconView;
@@ -202,9 +200,6 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
// This ensures then the next swipe up to all-apps starts from scroll 0.
activity.getAppsView().reset(false /* animate */);
// Optimization, hide the all apps view to prevent layout while initializing
activity.getAppsView().getContentView().setVisibility(View.GONE);
return new AnimationFactory() {
private ShelfAnimState mShelfState;
private boolean mIsAttachedToWindow;
@@ -24,7 +24,7 @@ public class QuickstepTestInformationHandler extends TestInformationHandler {
switch (method) {
case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
final float swipeHeight =
OverviewState.getDefaultSwipeHeight(mDeviceProfile);
OverviewState.getDefaultSwipeHeight(mContext, mDeviceProfile);
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
return response;
}
@@ -1,3 +1,4 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
@@ -82,7 +83,8 @@ public class AssistantTouchConsumer extends DelegateInputConsumer {
private int mDirection;
private ActivityControlHelper mActivityControlHelper;
private final float mDistThreshold;
private final float mDragDistThreshold;
private final float mFlingDistThreshold;
private final long mTimeThreshold;
private final int mAngleThreshold;
private final float mSquaredSlop;
@@ -97,7 +99,8 @@ public class AssistantTouchConsumer extends DelegateInputConsumer {
final Resources res = context.getResources();
mContext = context;
mSysUiProxy = systemUiProxy;
mDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
mDragDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
mFlingDistThreshold = res.getDimension(R.dimen.gestures_assistant_fling_threshold);
mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold);
mAngleThreshold = res.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
@@ -117,8 +120,6 @@ public class AssistantTouchConsumer extends DelegateInputConsumer {
@Override
public void onMotionEvent(MotionEvent ev) {
// TODO add logging
mGestureDetector.onTouchEvent(ev);
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
mActivePointerId = ev.getPointerId(0);
@@ -213,6 +214,8 @@ public class AssistantTouchConsumer extends DelegateInputConsumer {
break;
}
mGestureDetector.onTouchEvent(ev);
if (mState != STATE_ACTIVE) {
mDelegate.onMotionEvent(ev);
}
@@ -220,9 +223,9 @@ public class AssistantTouchConsumer extends DelegateInputConsumer {
private void updateAssistantProgress() {
if (!mLaunchedAssistant) {
mLastProgress = Math.min(mDistance * 1f / mDistThreshold, 1) * mTimeFraction;
mLastProgress = Math.min(mDistance * 1f / mDragDistThreshold, 1) * mTimeFraction;
try {
if (mDistance >= mDistThreshold && mTimeFraction >= 1) {
if (mDistance >= mDragDistThreshold && mTimeFraction >= 1) {
mSysUiProxy.onAssistantGestureCompletion(0);
startAssistantInternal(SWIPE);
@@ -271,7 +274,9 @@ public class AssistantTouchConsumer extends DelegateInputConsumer {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (isValidAssistantGestureAngle(velocityX, -velocityY)
&& !mLaunchedAssistant && mState != STATE_DELEGATE_ACTIVE) {
&& mDistance >= mFlingDistThreshold
&& !mLaunchedAssistant
&& mState != STATE_DELEGATE_ACTIVE) {
mLastProgress = 1;
try {
mSysUiProxy.onAssistantGestureCompletion(
@@ -29,6 +29,7 @@ import android.view.ViewGroup;
import androidx.annotation.Nullable;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
@@ -40,7 +41,9 @@ import com.android.launcher3.Workspace;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.anim.SpringObjectAnimator;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.views.IconLabelDotView;
import java.util.ArrayList;
import java.util.List;
@@ -62,7 +65,9 @@ public class StaggeredWorkspaceAnim {
private final float mVelocity;
private final float mSpringTransY;
private final View mViewToIgnore;
// The original view of the {@link FloatingIconView}.
private final View mOriginalView;
private final List<Animator> mAnimators = new ArrayList<>();
@@ -72,9 +77,7 @@ public class StaggeredWorkspaceAnim {
public StaggeredWorkspaceAnim(Launcher launcher, @Nullable View floatingViewOriginalView,
float velocity) {
mVelocity = velocity;
// We ignore this view since it's visibility and position is controlled by
// the FloatingIconView.
mViewToIgnore = floatingViewOriginalView;
mOriginalView = floatingViewOriginalView;
// Scale the translationY based on the initial velocity to better sync the workspace items
// with the floating view.
@@ -86,16 +89,21 @@ public class StaggeredWorkspaceAnim {
Workspace workspace = launcher.getWorkspace();
CellLayout cellLayout = (CellLayout) workspace.getChildAt(workspace.getCurrentPage());
ShortcutAndWidgetContainer currentPage = cellLayout.getShortcutsAndWidgets();
ViewGroup hotseat = launcher.getHotseat();
boolean workspaceClipChildren = workspace.getClipChildren();
boolean workspaceClipToPadding = workspace.getClipToPadding();
boolean cellLayoutClipChildren = cellLayout.getClipChildren();
boolean cellLayoutClipToPadding = cellLayout.getClipToPadding();
boolean hotseatClipChildren = hotseat.getClipChildren();
boolean hotseatClipToPadding = hotseat.getClipToPadding();
workspace.setClipChildren(false);
workspace.setClipToPadding(false);
cellLayout.setClipChildren(false);
cellLayout.setClipToPadding(false);
hotseat.setClipChildren(false);
hotseat.setClipToPadding(false);
// Hotseat and QSB takes up two additional rows.
int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2);
@@ -108,16 +116,18 @@ public class StaggeredWorkspaceAnim {
}
// Set up springs for the hotseat and qsb.
ViewGroup hotseatChild = (ViewGroup) hotseat.getChildAt(0);
if (grid.isVerticalBarLayout()) {
ViewGroup hotseat = (ViewGroup) launcher.getHotseat().getChildAt(0);
for (int i = hotseat.getChildCount() - 1; i >= 0; i--) {
View child = hotseat.getChildAt(i);
for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) {
View child = hotseatChild.getChildAt(i);
CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
addStaggeredAnimationForView(child, lp.cellY + 1, totalRows);
}
} else {
View hotseat = launcher.getHotseat().getChildAt(0);
addStaggeredAnimationForView(hotseat, grid.inv.numRows + 1, totalRows);
for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) {
View child = hotseatChild.getChildAt(i);
addStaggeredAnimationForView(child, grid.inv.numRows + 1, totalRows);
}
View qsb = launcher.findViewById(R.id.search_container_all_apps);
addStaggeredAnimationForView(qsb, grid.inv.numRows + 2, totalRows);
@@ -140,6 +150,8 @@ public class StaggeredWorkspaceAnim {
workspace.setClipToPadding(workspaceClipToPadding);
cellLayout.setClipChildren(cellLayoutClipChildren);
cellLayout.setClipToPadding(cellLayoutClipToPadding);
hotseat.setClipChildren(hotseatClipChildren);
hotseat.setClipToPadding(hotseatClipToPadding);
}
};
@@ -180,16 +192,35 @@ public class StaggeredWorkspaceAnim {
springTransY.setStartDelay(startDelay);
mAnimators.add(springTransY);
if (v == mViewToIgnore) {
return;
ObjectAnimator alpha = getAlphaAnimator(v, startDelay);
if (v == mOriginalView) {
// For IconLabelDotViews, we just want the label to fade in.
// Icon, badge, and dots will animate in separately (controlled via FloatingIconView)
if (v instanceof IconLabelDotView) {
alpha.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
IconLabelDotView view = (IconLabelDotView) v;
view.setIconVisible(false);
view.setForceHideDot(true);
}
});
} else {
return;
}
}
v.setAlpha(0);
mAnimators.add(alpha);
}
private ObjectAnimator getAlphaAnimator(View v, long startDelay) {
ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f);
alpha.setInterpolator(LINEAR);
alpha.setDuration(ALPHA_DURATION_MS);
alpha.setStartDelay(startDelay);
mAnimators.add(alpha);
return alpha;
}
private void addScrimAnimationForState(Launcher launcher, LauncherState state, long duration) {
+1
View File
@@ -69,6 +69,7 @@
<!-- Distance from the vertical edges of the screen in which assist gestures are recognized -->
<dimen name="gestures_assistant_width">48dp</dimen>
<dimen name="gestures_assistant_drag_threshold">55dp</dimen>
<dimen name="gestures_assistant_fling_threshold">55dp</dimen>
<!-- Distance to move elements when swiping up to go home from launcher -->
<dimen name="home_pullback_distance">28dp</dimen>
@@ -16,17 +16,34 @@
package com.android.launcher3.uioverrides;
import android.content.Context;
import android.provider.DeviceConfig;
import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
public class TogglableFlag extends BaseTogglableFlag {
public static final String NAMESPACE_LAUNCHER = "launcher";
public static final String TAG = "TogglableFlag";
public TogglableFlag(String key, boolean defaultValue, String description) {
super(key, defaultValue, description);
}
@Override
public boolean getInitialValue(boolean value) {
return DeviceConfig.getBoolean("launcher", getKey(), value);
public boolean getOverridenDefaultValue(boolean value) {
return DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, getKey(), value);
}
@Override
public void addChangeListener(Context context, Runnable r) {
DeviceConfig.addOnPropertiesChangedListener(
NAMESPACE_LAUNCHER,
context.getMainExecutor(),
(properties) -> {
if (!NAMESPACE_LAUNCHER.equals(properties.getNamespace())) {
return;
}
initialize(context);
r.run();
});
}
}
@@ -156,12 +156,14 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis
mDragHandleProgress = 1;
mMidAlpha = 0;
} else {
mMidAlpha = Themes.getAttrInteger(getContext(), R.attr.allAppsInterimScrimAlpha);
Context context = getContext();
mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha);
mMidProgress = OVERVIEW.getVerticalProgress(mLauncher);
Rect hotseatPadding = dp.getHotseatLayoutPadding();
int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom
- hotseatPadding.bottom - hotseatPadding.top;
float dragHandleTop = Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(dp));
float dragHandleTop =
Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(context, dp));
mDragHandleProgress = 1 - (dragHandleTop / mShiftRange);
}
mTopOffset = dp.getInsets().top - mShelfOffset;
@@ -69,10 +69,9 @@ public class DigitalWellBeingToastTest extends AbstractQuickStepTest {
private DigitalWellBeingToast getToast() {
executeOnLauncher(launcher -> launcher.getStateManager().goToState(OVERVIEW));
waitForState("Launcher internal state didn't switch to Overview", OVERVIEW);
waitForLauncherCondition("No latest task", launcher -> getLatestTask(launcher) != null);
final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher));
return getFromLauncher(launcher -> {
final TaskView task = getLatestTask(launcher);
assertTrue("Latest task is not Calculator",
CALCULATOR_PACKAGE.equals(task.getTask().getTopComponent().getPackageName()));
return task.getDigitalWellBeingToast();
@@ -118,7 +118,6 @@ public class NavigationModeSwitchRule implements TestRule {
assertTrue("Couldn't set overlay",
setActiveOverlay(prevOverlayPkg, originalMode));
}
mLauncher.disableDebugTracing();
}
private void evaluateWithThreeButtons() throws Throwable {
@@ -97,7 +97,6 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
@Test
@PortraitLandscape
public void testOverview() throws Exception {
mLauncher.enableDebugTracing();
startTestApps();
Overview overview = mLauncher.pressHome().switchToOverview();
assertTrue("Launcher internal state didn't switch to Overview",
@@ -177,7 +176,6 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
executeOnLauncher(
launcher -> assertEquals("Still have tasks after dismissing all",
0, getTaskCount(launcher)));
mLauncher.disableDebugTracing();
}
private int getCurrentOverviewPage(Launcher launcher) {
@@ -22,6 +22,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.views.RecyclerViewFastScroller;
import androidx.recyclerview.widget.RecyclerView;
@@ -171,4 +172,13 @@ public abstract class BaseRecyclerView extends RecyclerView {
* <p>Override in each subclass of this base class.
*/
public void onFastScrollCompleted() {}
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
if (state == SCROLL_STATE_IDLE) {
AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
}
}
}
@@ -32,7 +32,6 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Property;
import android.util.TypedValue;
import android.view.KeyEvent;
@@ -54,8 +53,8 @@ import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconCache.IconLoadRequest;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.IconLabelDotView;
import java.text.NumberFormat;
@@ -64,7 +63,8 @@ import java.text.NumberFormat;
* because we want to make the bubble taller than the text and TextView's clip is
* too aggressive.
*/
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback {
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
IconLabelDotView {
private static final int DISPLAY_WORKSPACE = 0;
private static final int DISPLAY_ALL_APPS = 1;
@@ -413,7 +413,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
}
}
public void forceHideDot(boolean forceHideDot) {
@Override
public void setForceHideDot(boolean forceHideDot) {
if (mForceHideDot == forceHideDot) {
return;
}
@@ -602,6 +603,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
}
}
@Override
public void setIconVisible(boolean visible) {
mIsIconVisible = visible;
Drawable icon = visible ? mIcon : new ColorDrawable(Color.TRANSPARENT);
@@ -142,8 +142,11 @@ public class FastBitmapDrawable extends Drawable {
@Override
public void setAlpha(int alpha) {
mAlpha = alpha;
mPaint.setAlpha(alpha);
if (mAlpha != alpha) {
mAlpha = alpha;
mPaint.setAlpha(alpha);
invalidateSelf();
}
}
@Override
+3 -2
View File
@@ -157,6 +157,7 @@ import java.util.List;
import java.util.function.Predicate;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
/**
* Default launcher application.
@@ -209,9 +210,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
// How long to wait before the new-shortcut animation automatically pans the workspace
private static final int NEW_APPS_PAGE_MOVE_DELAY = 500;
@VisibleForTesting public static final int NEW_APPS_PAGE_MOVE_DELAY = 500;
private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
@Thunk static final int NEW_APPS_ANIMATION_DELAY = 500;
@Thunk @VisibleForTesting public static final int NEW_APPS_ANIMATION_DELAY = 500;
private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 1;
private static final int SCRIM_VIEW_ALPHA_CHANNEL_INDEX = 0;
@@ -94,6 +94,7 @@ public class LauncherAppState {
if (FeatureFlags.IS_DOGFOOD_BUILD) {
filter.addAction(ACTION_FORCE_ROLOAD);
}
FeatureFlags.APP_SEARCH_IMPROVEMENTS.addChangeListener(context, mModel::forceReload);
mContext.registerReceiver(mModel, filter);
UserManagerCompat.getInstance(mContext).enableAndResetCache();
@@ -20,6 +20,7 @@ import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
@@ -51,6 +52,7 @@ import com.android.launcher3.model.UserLockStateChangedTask;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
@@ -211,6 +213,30 @@ public class LauncherModel extends BroadcastReceiver
enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
}
public void onSessionFailure(String packageName, UserHandle user) {
enqueueModelUpdateTask(new BaseModelUpdateTask() {
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
final IntSparseArrayMap<Boolean> removedIds = new IntSparseArrayMap<>();
synchronized (dataModel) {
for (ItemInfo info : dataModel.itemsIdMap) {
if (info instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) info).hasPromiseIconUi()
&& user.equals(info.user)
&& info.getIntent() != null
&& TextUtils.equals(packageName, info.getIntent().getPackage())) {
removedIds.put(info.id, true /* remove */);
}
}
}
if (!removedIds.isEmpty()) {
deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedIds, false));
}
}
});
}
@Override
public void onPackageRemoved(String packageName, UserHandle user) {
onPackagesRemoved(user, packageName);
@@ -228,8 +228,9 @@ public class LauncherStateManager {
private void goToState(LauncherState state, boolean animated, long delay,
final Runnable onCompleteRunnable) {
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "goToState: " + state + " @ " +
Log.getStackTraceString(new Throwable()));
Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "goToState: " +
state.getClass().getSimpleName() +
" @ " + Log.getStackTraceString(new Throwable()));
}
animated &= Utilities.areAnimationsEnabled(mLauncher);
if (mLauncher.isInState(state)) {
@@ -411,6 +412,11 @@ public class LauncherStateManager {
mState.onStateDisabled(mLauncher);
}
mState = state;
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.STABLE_STATE_MISMATCH, "onStateTransitionStart: " +
state.getClass().getSimpleName() +
" @ " + Log.getStackTraceString(new Throwable()));
}
mState.onStateEnabled(mLauncher);
mLauncher.onStateSet(mState);
@@ -431,7 +437,9 @@ public class LauncherStateManager {
mLastStableState = state.getHistoryForState(mCurrentStableState);
mCurrentStableState = state;
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "onStateTransitionEnd: " + state);
Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "onStateTransitionEnd: " +
state.getClass().getSimpleName() +
" @ " + Log.getStackTraceString(new Throwable()));
}
}
@@ -1,5 +1,7 @@
package com.android.launcher3.accessibility;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
import static com.android.launcher3.LauncherState.NORMAL;
import android.app.AlertDialog;
@@ -30,14 +32,14 @@ import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.Workspace;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.dragndrop.DragController.DragListener;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.keyboard.CustomActionsPopup;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.ShortcutUtil;
@@ -164,6 +166,13 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme
}
public boolean performAction(final View host, final ItemInfo item, int action) {
if (action == ACTION_LONG_CLICK && ShortcutUtil.isDeepShortcut(item)) {
CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
if (popup.canShow()) {
popup.show();
return true;
}
}
if (action == MOVE) {
beginAccessibleDrag(host, item);
} else if (action == ADD_TO_WORKSPACE) {
@@ -26,13 +26,15 @@ import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -41,8 +43,6 @@ import com.android.launcher3.views.RecyclerViewFastScroller;
import java.util.List;
import androidx.recyclerview.widget.RecyclerView;
/**
* A RecyclerView with custom fast scroll support for the all apps view.
*/
@@ -114,6 +114,13 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine
if (mScrollbar != null) {
mScrollbar.reattachThumbToScroll();
}
if (getLayoutManager() instanceof AppsGridLayoutManager) {
AppsGridLayoutManager layoutManager = (AppsGridLayoutManager) getLayoutManager();
if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
// We are at the top, so don't scrollToPosition (would cause unnecessary relayout).
return;
}
}
scrollToPosition(0);
}
@@ -420,13 +427,4 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine
public boolean hasOverlappingRendering() {
return false;
}
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
if (state == SCROLL_STATE_IDLE) {
AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
}
}
}
@@ -18,7 +18,6 @@ import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.util.FloatProperty;
import android.util.Log;
import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile;
@@ -32,7 +31,6 @@ import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.anim.SpringObjectAnimator;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ScrimView;
@@ -0,0 +1,229 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.anim;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.FloatProperty;
import com.android.launcher3.util.DefaultDisplay;
import androidx.annotation.FloatRange;
import androidx.dynamicanimation.animation.SpringForce;
/**
* Utility class to build an object animator which follows the same path as a spring animation for
* an underdamped spring.
*/
public class SpringAnimationBuilder<T> extends FloatProperty<T> {
private final T mTarget;
private final FloatProperty<T> mProperty;
private float mStartValue;
private float mEndValue;
private float mVelocity = 0;
private float mStiffness = SpringForce.STIFFNESS_MEDIUM;
private float mDampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
private float mMinVisibleChange = 1;
// Multiplier to the min visible change value for value threshold
private static final float THRESHOLD_MULTIPLIER = 0.65f;
/**
* The spring equation is given as
* x = e^(-beta*t/2) * (a cos(gamma * t) + b sin(gamma * t)
* v = e^(-beta*t/2) * ((2 * a * gamma + beta * b) * sin(gamma * t)
* + (a * beta - 2 * b * gamma) * cos(gamma * t)) / 2
*
* a = x(0)
* b = beta * x(0) / (2 * gamma) + v(0) / gamma
*/
private double beta;
private double gamma;
private double a, b;
private double va, vb;
// Threshold for velocity and value to determine when it's reasonable to assume that the spring
// is approximately at rest.
private double mValueThreshold;
private double mVelocityThreshold;
private float mCurrentTime = 0;
public SpringAnimationBuilder(T target, FloatProperty<T> property) {
super("dynamic-spring-property");
mTarget = target;
mProperty = property;
mStartValue = mProperty.get(target);
}
public SpringAnimationBuilder<T> setEndValue(float value) {
mEndValue = value;
return this;
}
public SpringAnimationBuilder<T> setStartValue(float value) {
mStartValue = value;
return this;
}
public SpringAnimationBuilder<T> setValues(float... values) {
if (values.length > 1) {
mStartValue = values[0];
mEndValue = values[values.length - 1];
} else {
mEndValue = values[0];
}
return this;
}
public SpringAnimationBuilder<T> setStiffness(
@FloatRange(from = 0.0, fromInclusive = false) float stiffness) {
if (stiffness <= 0) {
throw new IllegalArgumentException("Spring stiffness constant must be positive.");
}
mStiffness = stiffness;
return this;
}
public SpringAnimationBuilder<T> setDampingRatio(
@FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
float dampingRatio) {
if (dampingRatio <= 0 || dampingRatio >= 1) {
throw new IllegalArgumentException("Damping ratio must be between 0 and 1");
}
mDampingRatio = dampingRatio;
return this;
}
public SpringAnimationBuilder<T> setMinimumVisibleChange(
@FloatRange(from = 0.0, fromInclusive = false) float minimumVisibleChange) {
if (minimumVisibleChange <= 0) {
throw new IllegalArgumentException("Minimum visible change must be positive.");
}
mMinVisibleChange = minimumVisibleChange;
return this;
}
public SpringAnimationBuilder<T> setStartVelocity(float startVelocity) {
mVelocity = startVelocity;
return this;
}
@Override
public void setValue(T object, float time) {
mCurrentTime = time;
mProperty.setValue(
object, (float) (exponentialComponent(time) * cosSinX(time)) + mEndValue);
}
@Override
public Float get(T t) {
return mCurrentTime;
}
public ObjectAnimator build(Context context) {
int singleFrameMs = DefaultDisplay.getSingleFrameMs(context);
double naturalFreq = Math.sqrt(mStiffness);
double dampedFreq = naturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio);
// All the calculations assume the stable position to be 0, shift the values accordingly.
beta = 2 * mDampingRatio * naturalFreq;
gamma = dampedFreq;
a = mStartValue - mEndValue;
b = beta * a / (2 * gamma) + mVelocity / gamma;
va = a * beta / 2 - b * gamma;
vb = a * gamma + beta * b / 2;
mValueThreshold = mMinVisibleChange * THRESHOLD_MULTIPLIER;
// This multiplier is used to calculate the velocity threshold given a certain value
// threshold. The idea is that if it takes >= 1 frame to move the value threshold amount,
// then the velocity is a reasonable threshold.
mVelocityThreshold = mValueThreshold * 1000.0 / singleFrameMs;
// Find the duration (in seconds) for the spring to reach equilibrium.
// equilibrium is reached when x = 0
double duration = Math.atan2(-a, b) / gamma;
// Keep moving ahead until the velocity reaches equilibrium.
double piByG = Math.PI / gamma;
while (duration < 0 || Math.abs(exponentialComponent(duration) * cosSinV(duration))
>= mVelocityThreshold) {
duration += piByG;
}
// Find the shortest time
double edgeTime = Math.max(0, duration - piByG / 2);
double minDiff = singleFrameMs / 2000.0; // Half frame time in seconds
do {
if ((duration - edgeTime) < minDiff) {
break;
}
double mid = (edgeTime + duration) / 2;
if (isAtEquilibrium(mid)) {
duration = mid;
} else {
edgeTime = mid;
}
} while (true);
long durationMs = (long) (1000.0 * duration);
ObjectAnimator animator = ObjectAnimator.ofFloat(mTarget, this, 0, (float) duration);
animator.setDuration(durationMs).setInterpolator(Interpolators.LINEAR);
animator.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
mProperty.setValue(mTarget, mEndValue);
}
});
return animator;
}
private boolean isAtEquilibrium(double t) {
double ec = exponentialComponent(t);
if (Math.abs(ec * cosSinX(t)) >= mValueThreshold) {
return false;
}
return Math.abs(ec * cosSinV(t)) < mVelocityThreshold;
}
private double exponentialComponent(double t) {
return Math.pow(Math.E, - beta * t / 2);
}
private double cosSinX(double t) {
return cosSin(t, a, b);
}
private double cosSinV(double t) {
return cosSin(t, va, vb);
}
private double cosSin(double t, double cosFactor, double sinFactor) {
double angle = t * gamma;
return cosFactor * Math.cos(angle) + sinFactor * Math.sin(angle);
}
}
@@ -136,6 +136,30 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat {
}
}
/**
* Add a promise app icon to the workspace iff:
* - The settings for it are enabled
* - The user installed the app
* - There is an app icon and label (For apps with no launching activity, no icon is provided).
* - The app is not already installed
* - A promise icon for the session has not already been created
*/
private void tryQueuePromiseAppIcon(SessionInfo sessionInfo) {
if (Utilities.ATLEAST_OREO && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
&& SessionCommitReceiver.isEnabled(mAppContext)
&& verify(sessionInfo) != null
&& sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
&& sessionInfo.getAppIcon() != null
&& !TextUtils.isEmpty(sessionInfo.getAppLabel())
&& !mPromiseIconIds.contains(sessionInfo.getSessionId())
&& mLauncherApps.getApplicationInfo(sessionInfo.getAppPackageName(), 0,
getUserHandle(sessionInfo)) == null) {
SessionCommitReceiver.queuePromiseAppIconAddition(mAppContext, sessionInfo);
mPromiseIconIds.add(sessionInfo.getSessionId());
updatePromiseIconPrefs();
}
}
private final SessionCallback mCallback = new SessionCallback() {
@Override
@@ -149,16 +173,7 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat {
}
}
if (Utilities.ATLEAST_OREO && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
&& SessionCommitReceiver.isEnabled(mAppContext)
&& sessionInfo != null
&& sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER) {
SessionCommitReceiver.queuePromiseAppIconAddition(mAppContext, sessionInfo);
if (!mPromiseIconIds.contains(sessionInfo.getSessionId())) {
mPromiseIconIds.add(sessionInfo.getSessionId());
updatePromiseIconPrefs();
}
}
tryQueuePromiseAppIcon(sessionInfo);
}
@Override
@@ -173,12 +188,14 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat {
sendUpdate(PackageInstallInfo.fromState(success ? STATUS_INSTALLED : STATUS_FAILED,
packageName, key.mUser));
if (!success && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()) {
if (!success && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
&& mPromiseIconIds.contains(sessionId)) {
LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
if (appState != null) {
LauncherModel model = appState.getModel();
model.onPackageRemoved(packageName, key.mUser);
appState.getModel().onSessionFailure(packageName, key.mUser);
}
// If it is successful, the id is removed in the the package added flow.
removePromiseIconId(sessionId);
}
}
}
@@ -196,7 +213,10 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat {
@Override
public void onBadgingChanged(int sessionId) {
pushSessionDisplayToLauncher(sessionId);
SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId);
if (sessionInfo != null) {
tryQueuePromiseAppIcon(sessionInfo);
}
}
private SessionInfo pushSessionDisplayToLauncher(int sessionId) {
@@ -143,6 +143,8 @@ public abstract class BaseFlags {
public static abstract class BaseTogglableFlag {
private final String key;
// should be value that is hardcoded in client side.
// Comparatively, getDefaultValue() can be overridden.
private final boolean defaultValue;
private final String description;
private boolean currentValue;
@@ -152,8 +154,9 @@ public abstract class BaseFlags {
boolean defaultValue,
String description) {
this.key = checkNotNull(key);
this.currentValue = this.defaultValue = getInitialValue(defaultValue);
this.currentValue = this.defaultValue = defaultValue;
this.description = checkNotNull(description);
synchronized (sLock) {
sFlags.add((TogglableFlag)this);
}
@@ -169,16 +172,18 @@ public abstract class BaseFlags {
return key;
}
void initialize(Context context) {
currentValue = getFromStorage(context, defaultValue);
protected void initialize(Context context) {
currentValue = getFromStorage(context, getDefaultValue());
}
protected abstract boolean getInitialValue(boolean value);
protected abstract boolean getOverridenDefaultValue(boolean value);
protected abstract void addChangeListener(Context context, Runnable r);
public void updateStorage(Context context, boolean value) {
SharedPreferences.Editor editor = context.getSharedPreferences(FLAGS_PREF_NAME,
Context.MODE_PRIVATE).edit();
if (value == defaultValue) {
if (value == getDefaultValue()) {
editor.remove(key).apply();
} else {
editor.putBoolean(key, value).apply();
@@ -187,11 +192,11 @@ public abstract class BaseFlags {
boolean getFromStorage(Context context, boolean defaultValue) {
return context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE)
.getBoolean(key, defaultValue);
.getBoolean(key, getDefaultValue());
}
boolean getDefaultValue() {
return defaultValue;
return getOverridenDefaultValue(defaultValue);
}
/** Returns the value of the flag at process start, including any overrides present. */
@@ -208,6 +213,8 @@ public abstract class BaseFlags {
return "TogglableFlag{"
+ "key=" + key + ", "
+ "defaultValue=" + defaultValue + ", "
+ "overriddenDefaultValue=" + getOverridenDefaultValue(defaultValue) + ", "
+ "currentValue=" + currentValue + ", "
+ "description=" + description
+ "}";
}
@@ -220,7 +227,7 @@ public abstract class BaseFlags {
if (o instanceof TogglableFlag) {
BaseTogglableFlag that = (BaseTogglableFlag) o;
return (this.key.equals(that.getKey()))
&& (this.defaultValue == that.getDefaultValue())
&& (this.getDefaultValue() == that.getDefaultValue())
&& (this.description.equals(that.getDescription()));
}
return false;
@@ -232,7 +239,7 @@ public abstract class BaseFlags {
h$ *= 1000003;
h$ ^= key.hashCode();
h$ *= 1000003;
h$ ^= defaultValue ? 1231 : 1237;
h$ ^= getDefaultValue() ? 1231 : 1237;
h$ *= 1000003;
h$ ^= description.hashCode();
return h$;
@@ -61,6 +61,9 @@ public abstract class DragDriver {
mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_CANCEL:
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragDriver.ACTION_CANCEL");
}
mEventListener.onDriverDragCancel();
break;
}
+2 -2
View File
@@ -516,7 +516,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mFolderIcon.setBackgroundVisible(false);
mFolderIcon.setIconVisible(false);
mFolderIcon.drawLeaveBehindIfExists();
}
@Override
@@ -646,7 +646,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
clearFocus();
if (mFolderIcon != null) {
mFolderIcon.setVisibility(View.VISIBLE);
mFolderIcon.setBackgroundVisible(true);
mFolderIcon.setIconVisible(true);
mFolderIcon.mFolderName.setTextVisibility(true);
if (wasAnimated) {
mFolderIcon.animateBgShadowAndStroke();
@@ -65,6 +65,7 @@ import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.IconLabelDotView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import java.util.ArrayList;
@@ -73,7 +74,7 @@ import java.util.List;
/**
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
public class FolderIcon extends FrameLayout implements FolderListener {
public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView {
@Thunk Launcher mLauncher;
@Thunk Folder mFolder;
@@ -107,6 +108,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
private Alarm mOpenAlarm = new Alarm();
private boolean mForceHideDot;
@ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
private FolderDotInfo mDotInfo = new FolderDotInfo();
private DotRenderer mDotRenderer;
@@ -409,6 +411,20 @@ public class FolderIcon extends FrameLayout implements FolderListener {
return mPreviewLayoutRule;
}
@Override
public void setForceHideDot(boolean forceHideDot) {
if (mForceHideDot == forceHideDot) {
return;
}
mForceHideDot = forceHideDot;
if (forceHideDot) {
invalidate();
} else if (hasDot()) {
animateDotScale(0, 1);
}
}
/**
* Sets mDotScale to 1 or 0, animating if wasDotted or isDotted is false
* (the dot is being added or removed).
@@ -468,7 +484,8 @@ public class FolderIcon extends FrameLayout implements FolderListener {
mBackground.setInvalidateDelegate(this);
}
public void setBackgroundVisible(boolean visible) {
@Override
public void setIconVisible(boolean visible) {
mBackgroundIsVisible = visible;
invalidate();
}
@@ -509,7 +526,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
public void drawDot(Canvas canvas) {
if ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0) {
if (!mForceHideDot && ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0)) {
Rect iconBounds = mDotParams.iconBounds;
BubbleTextView.getIconBounds(this, iconBounds,
mLauncher.getWallpaperDeviceProfile().iconSizePx);
@@ -42,6 +42,7 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
import com.android.launcher3.icons.cache.BaseIconCache;
import com.android.launcher3.icons.cache.CachingLogic;
@@ -237,7 +238,8 @@ public class IconCache extends BaseIconCache {
@Override
protected String getIconSystemState(String packageName) {
return mIconProvider.getSystemStateForPackage(mSystemState, packageName);
return mIconProvider.getSystemStateForPackage(mSystemState, packageName)
+ ",flags_asi:" + FeatureFlags.APP_SEARCH_IMPROVEMENTS.get();
}
public static abstract class IconLoadRequest extends HandlerRunnable {
@@ -36,7 +36,6 @@ import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
@@ -53,7 +52,6 @@ import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
import com.android.launcher3.dot.DotInfo;
@@ -65,10 +63,8 @@ import com.android.launcher3.notification.NotificationInfo;
import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.PackageUserKey;
@@ -301,7 +297,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
}
mLauncher.getDragController().addDragListener(this);
mOriginalIcon.forceHideDot(true);
mOriginalIcon.setForceHideDot(true);
// All views are added. Animate layout from now on.
setLayoutTransition(new LayoutTransition());
@@ -564,14 +560,14 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
protected void onCreateCloseAnimation(AnimatorSet anim) {
// Animate original icon's text back in.
anim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */));
mOriginalIcon.forceHideDot(false);
mOriginalIcon.setForceHideDot(false);
}
@Override
protected void closeComplete() {
super.closeComplete();
mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
mOriginalIcon.forceHideDot(false);
mOriginalIcon.setForceHideDot(false);
}
@Override
@@ -173,12 +173,6 @@ public class RestoreDbTask {
values.put(Favorites.PROFILE_ID, newProfileId);
db.update(Favorites.TABLE_NAME, values, "profileId = ?",
new String[]{Long.toString(oldProfileId)});
// Change default value of the column.
db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
Favorites.addTableToDb(db, newProfileId, false);
db.execSQL("INSERT INTO favorites SELECT * FROM favorites_old;");
dropTable(db, "favorites_old");
}
@@ -17,6 +17,7 @@ package com.android.launcher3.testing;
import android.content.Context;
import android.os.Bundle;
import android.os.Debug;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
@@ -113,13 +114,13 @@ public class TestInformationHandler implements ResourceBasedOverride {
break;
}
case TestProtocol.REQUEST_ALLOCATED_MEMORY: {
final Runtime runtime = Runtime.getRuntime();
response.putLong(TestProtocol.TEST_INFO_RESPONSE_FIELD,
runtime.totalMemory() - runtime.freeMemory());
case TestProtocol.REQUEST_TOTAL_PSS_KB: {
Debug.MemoryInfo mem = new Debug.MemoryInfo();
Debug.getMemoryInfo(mem);
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, mem.getTotalPss());
break;
}
}
return response;
}
}
}
@@ -73,7 +73,7 @@ public final class TestProtocol {
public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags";
public static final String REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN = "overview-left-margin";
public static final String REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN = "overview-right-margin";
public static final String REQUEST_ALLOCATED_MEMORY = "allocated-memory";
public static final String REQUEST_TOTAL_PSS_KB = "total_pss";
public static boolean sDebugTracing = false;
public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
@@ -83,4 +83,5 @@ public final class TestProtocol {
public static final String NO_DRAG_TO_WORKSPACE = "b/138729456";
public static final String APP_NOT_DISABLED = "b/139891609";
public static final String ALL_APPS_UPON_RECENTS = "b/139941530";
public static final String STABLE_STATE_MISMATCH = "b/140311911";
}
@@ -23,37 +23,57 @@ import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.shortcuts.ShortcutKey;
public class ShortcutUtil {
public static boolean supportsShortcuts(ItemInfo info) {
return isActive(info) && (isApp(info) || isPinnedShortcut(info));
}
/**
* Returns true when we should show shortcut menu for the item.
*/
public static boolean supportsShortcuts(ItemInfo info) {
return isActive(info) && (isApp(info) || isPinnedShortcut(info));
}
public static boolean supportsDeepShortcuts(ItemInfo info) {
return isActive(info) && isApp(info);
}
/**
* Returns true when we should show depp shortcuts in shortcut menu for the item.
*/
public static boolean supportsDeepShortcuts(ItemInfo info) {
return isActive(info) && isApp(info);
}
public static String getShortcutIdIfPinnedShortcut(ItemInfo info) {
return isActive(info) && isPinnedShortcut(info) ?
ShortcutKey.fromItemInfo(info).getId() : null;
}
/**
* Returns the shortcut id if the item is a pinned shortcut.
*/
public static String getShortcutIdIfPinnedShortcut(ItemInfo info) {
return isActive(info) && isPinnedShortcut(info)
? ShortcutKey.fromItemInfo(info).getId() : null;
}
public static String[] getPersonKeysIfPinnedShortcut(ItemInfo info) {
return isActive(info) && isPinnedShortcut(info) ?
((WorkspaceItemInfo) info).getPersonKeys() : Utilities.EMPTY_STRING_ARRAY;
}
/**
* Returns the person keys associated with the item. (Has no function right now.)
*/
public static String[] getPersonKeysIfPinnedShortcut(ItemInfo info) {
return isActive(info) && isPinnedShortcut(info)
? ((WorkspaceItemInfo) info).getPersonKeys() : Utilities.EMPTY_STRING_ARRAY;
}
private static boolean isActive(ItemInfo info) {
boolean isLoading = info instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) info).hasPromiseIconUi();
return !isLoading && !info.isDisabled() && !FeatureFlags.GO_DISABLE_WIDGETS;
}
/**
* Returns true if the item is a deep shortcut.
*/
public static boolean isDeepShortcut(ItemInfo info) {
return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
&& info instanceof WorkspaceItemInfo;
}
private static boolean isApp(ItemInfo info) {
return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
}
private static boolean isActive(ItemInfo info) {
boolean isLoading = info instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) info).hasPromiseIconUi();
return !isLoading && !info.isDisabled() && !FeatureFlags.GO_DISABLE_WIDGETS;
}
private static boolean isPinnedShortcut(ItemInfo info) {
return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
&& info.container != ItemInfo.NO_ID
&& info instanceof WorkspaceItemInfo;
}
private static boolean isApp(ItemInfo info) {
return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
}
private static boolean isPinnedShortcut(ItemInfo info) {
return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
&& info.container != ItemInfo.NO_ID
&& info instanceof WorkspaceItemInfo;
}
}
@@ -153,15 +153,15 @@ public abstract class AbstractSlideInView extends AbstractFloatingView
}
protected void handleClose(boolean animate, long defaultDuration) {
if (mIsOpen && !animate) {
if (!mIsOpen) {
return;
}
if (!animate) {
mOpenCloseAnimator.cancel();
setTranslationShift(TRANSLATION_SHIFT_CLOSED);
onCloseComplete();
return;
}
if (!mIsOpen) {
return;
}
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED));
mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@@ -29,6 +29,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Property;
import android.view.MotionEvent;
import android.view.View;
@@ -41,6 +42,7 @@ import android.widget.FrameLayout;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.Utilities;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.TouchController;
@@ -261,6 +263,10 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
}
case ACTION_CANCEL:
case ACTION_UP:
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE,
"BaseDragLayer.ACTION_UP/CANCEL " + ev);
}
mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
mTouchDispatchState &= ~TOUCH_DISPATCHING_VIEW;
break;
@@ -18,7 +18,6 @@ package com.android.launcher3.views;
import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
import static com.android.launcher3.Utilities.getBadge;
import static com.android.launcher3.Utilities.getFullDrawable;
import static com.android.launcher3.Utilities.isRtl;
import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
@@ -564,11 +563,6 @@ public class FloatingIconView extends View implements
*/
private void checkIconResult(View originalView, boolean isOpening) {
CancellationSignal cancellationSignal = new CancellationSignal();
if (!isOpening) {
// Hide immediately since the floating view starts at a different location.
originalView.setVisibility(INVISIBLE);
cancellationSignal.setOnCancelListener(() -> originalView.setVisibility(VISIBLE));
}
if (mIconLoadResult == null) {
Log.w(TAG, "No icon load result found in checkIconResult");
@@ -580,7 +574,7 @@ public class FloatingIconView extends View implements
setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
mIconLoadResult.iconOffset);
if (isOpening) {
originalView.setVisibility(INVISIBLE);
hideOriginalView(originalView);
}
} else {
mIconLoadResult.onIconLoaded = () -> {
@@ -591,15 +585,26 @@ public class FloatingIconView extends View implements
setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
mIconLoadResult.iconOffset);
// Delay swapping views until the icon is loaded to prevent a flash.
setVisibility(VISIBLE);
originalView.setVisibility(INVISIBLE);
if (isOpening) {
// Delay swapping views until the icon is loaded to prevent a flash.
hideOriginalView(originalView);
}
};
mLoadIconSignal = cancellationSignal;
}
}
}
private void hideOriginalView(View originalView) {
if (originalView instanceof BubbleTextView) {
((BubbleTextView) originalView).setIconVisible(false);
((BubbleTextView) originalView).setForceHideDot(true);
} else {
originalView.setVisibility(INVISIBLE);
}
}
private void setBackgroundDrawableBounds(float scale) {
sTmpRect.set(mFinalDrawableBounds);
Utilities.scaleRectAboutCenter(sTmpRect, scale);
@@ -716,7 +721,7 @@ public class FloatingIconView extends View implements
*/
@UiThread
public static IconLoadResult fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening) {
IconLoadResult result = new IconLoadResult();
IconLoadResult result = new IconLoadResult(info);
new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> {
RectF position = new RectF();
getLocationBoundsForView(l, v, isOpening, position);
@@ -745,10 +750,13 @@ public class FloatingIconView extends View implements
// Get the drawable on the background thread
boolean shouldLoadIcon = originalView.getTag() instanceof ItemInfo && hideOriginal;
view.mIconLoadResult = sIconLoadResult;
if (shouldLoadIcon && view.mIconLoadResult == null) {
view.mIconLoadResult = fetchIcon(launcher, originalView,
(ItemInfo) originalView.getTag(), isOpening);
if (shouldLoadIcon) {
if (sIconLoadResult != null && sIconLoadResult.itemInfo == originalView.getTag()) {
view.mIconLoadResult = sIconLoadResult;
} else {
view.mIconLoadResult = fetchIcon(launcher, originalView,
(ItemInfo) originalView.getTag(), isOpening);
}
}
sIconLoadResult = null;
@@ -776,7 +784,12 @@ public class FloatingIconView extends View implements
if (hideOriginal) {
if (isOpening) {
originalView.setVisibility(VISIBLE);
if (originalView instanceof BubbleTextView) {
((BubbleTextView) originalView).setIconVisible(true);
((BubbleTextView) originalView).setForceHideDot(false);
} else {
originalView.setVisibility(VISIBLE);
}
view.finish(dragLayer);
} else {
view.mFadeAnimatorSet = view.createFadeAnimation(originalView, dragLayer);
@@ -804,38 +817,33 @@ public class FloatingIconView extends View implements
}
});
if (mBadge != null && !(mOriginalIcon instanceof FolderIcon)) {
if (mBadge != null) {
ObjectAnimator badgeFade = ObjectAnimator.ofInt(mBadge, DRAWABLE_ALPHA, 255);
badgeFade.addUpdateListener(valueAnimator -> invalidate());
fade.play(badgeFade);
}
if (originalView instanceof BubbleTextView) {
BubbleTextView btv = (BubbleTextView) originalView;
btv.forceHideDot(true);
if (originalView instanceof IconLabelDotView) {
IconLabelDotView view = (IconLabelDotView) originalView;
fade.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
btv.forceHideDot(false);
view.setIconVisible(true);
view.setForceHideDot(false);
}
});
}
if (originalView instanceof FolderIcon) {
FolderIcon folderIcon = (FolderIcon) originalView;
folderIcon.setBackgroundVisible(false);
folderIcon.getFolderName().setTextVisibility(false);
fade.play(folderIcon.getFolderName().createTextAlphaAnimator(true));
if (originalView instanceof BubbleTextView) {
BubbleTextView btv = (BubbleTextView) originalView;
fade.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
folderIcon.setBackgroundVisible(true);
if (folderIcon.hasDot()) {
folderIcon.animateDotScale(0, 1f);
}
public void onAnimationStart(Animator animation) {
btv.setIconVisible(true);
}
});
} else {
fade.play(ObjectAnimator.ofInt(btv.getIcon(), DRAWABLE_ALPHA, 0, 255));
} else if (!(originalView instanceof FolderIcon)) {
fade.play(ObjectAnimator.ofFloat(originalView, ALPHA, 0f, 1f));
}
@@ -890,10 +898,15 @@ public class FloatingIconView extends View implements
}
private static class IconLoadResult {
final ItemInfo itemInfo;
Drawable drawable;
Drawable badge;
int iconOffset;
Runnable onIconLoaded;
boolean isIconLoaded;
public IconLoadResult(ItemInfo itemInfo) {
this.itemInfo = itemInfo;
}
}
}
@@ -0,0 +1,24 @@
/*
* 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.views;
/**
* A view that has an icon, label, and notification dot.
*/
public interface IconLabelDotView {
void setIconVisible(boolean visible);
void setForceHideDot(boolean hide);
}
@@ -18,10 +18,8 @@ package com.android.launcher3.views;
import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_FLAVOR;
import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.graphics.RectF;
import android.text.TextUtils;
@@ -33,6 +31,9 @@ import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -46,7 +47,6 @@ import com.android.launcher3.widget.WidgetsFullSheet;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.VisibleForTesting;
/**
* Popup shown on long pressing an empty space in launcher
@@ -169,16 +169,17 @@ public class OptionsPopupView extends ArrowPopup
}
public static boolean onWidgetsClicked(View view) {
return openWidgets(Launcher.getLauncher(view.getContext()));
return openWidgets(Launcher.getLauncher(view.getContext())) != null;
}
public static boolean openWidgets(Launcher launcher) {
/** Returns WidgetsFullSheet that was opened, or null if nothing was opened. */
@Nullable
public static WidgetsFullSheet openWidgets(Launcher launcher) {
if (launcher.getPackageManager().isSafeMode()) {
Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
return false;
return null;
} else {
WidgetsFullSheet.show(launcher, true /* animated */);
return true;
return WidgetsFullSheet.show(launcher, true /* animated */);
}
}
+29 -10
View File
@@ -18,14 +18,14 @@ package com.android.launcher3.views;
import static android.content.Context.ACCESSIBILITY_SERVICE;
import static android.view.MotionEvent.ACTION_DOWN;
import static androidx.core.graphics.ColorUtils.compositeColors;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import static androidx.core.graphics.ColorUtils.compositeColors;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.Keyframe;
@@ -47,6 +47,13 @@ import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
import androidx.customview.widget.ExploreByTouchHelper;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
@@ -62,15 +69,10 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.Themes;
import com.android.launcher3.widget.WidgetsFullSheet;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
import androidx.customview.widget.ExploreByTouchHelper;
/**
* Simple scrim which draws a flat color
@@ -325,7 +327,7 @@ public class ScrimView extends View implements Insettable, OnChangeListener,
if (enabled) {
stateManager.addStateListener(this);
handleStateChangedComplete(mLauncher.getStateManager().getState());
handleStateChangedComplete(stateManager.getState());
} else {
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
@@ -437,7 +439,24 @@ public class ScrimView extends View implements Insettable, OnChangeListener,
} else if (action == WALLPAPERS) {
return OptionsPopupView.startWallpaperPicker(ScrimView.this);
} else if (action == WIDGETS) {
return OptionsPopupView.onWidgetsClicked(ScrimView.this);
int originalImportanceForAccessibility = getImportantForAccessibility();
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
WidgetsFullSheet widgetsFullSheet = OptionsPopupView.openWidgets(mLauncher);
if (widgetsFullSheet == null) {
setImportantForAccessibility(originalImportanceForAccessibility);
return false;
}
widgetsFullSheet.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View view) {}
@Override
public void onViewDetachedFromWindow(View view) {
setImportantForAccessibility(originalImportanceForAccessibility);
widgetsFullSheet.removeOnAttachStateChangeListener(this);
}
});
return true;
} else if (action == SETTINGS) {
return OptionsPopupView.startSettings(ScrimView.this);
}
@@ -16,6 +16,7 @@
package com.android.launcher3.uioverrides;
import android.content.Context;
import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
public class TogglableFlag extends BaseTogglableFlag {
@@ -25,7 +26,10 @@ public class TogglableFlag extends BaseTogglableFlag {
}
@Override
public boolean getInitialValue(boolean value) {
public boolean getOverridenDefaultValue(boolean value) {
return value;
}
@Override
public void addChangeListener(Context context, Runnable r) { }
}
@@ -0,0 +1,113 @@
/*
* 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.compat;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.text.TextUtils;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.UUID;
/**
* Test to verify promise icon flow.
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
public class PromiseIconUiTest extends AbstractLauncherUiTest {
private int mSessionId = -1;
@Override
public void setUp() throws Exception {
super.setUp();
mDevice.pressHome();
waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
mSessionId = -1;
}
@After
public void tearDown() {
if (mSessionId > -1) {
mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
}
}
/**
* Create a session and return the id.
*/
private int createSession(String label, Bitmap icon) throws Throwable {
SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
params.setAppPackageName("test.promise.app");
params.setAppLabel(label);
params.setAppIcon(icon);
params.setInstallReason(PackageManager.INSTALL_REASON_USER);
return mTargetContext.getPackageManager().getPackageInstaller().createSession(params);
}
@Test
public void testPromiseIcon_addedFromEligibleSession() throws Throwable {
final String appLabel = "Test Promise App " + UUID.randomUUID().toString();
final Workspace.ItemOperator findPromiseApp = (info, view) ->
info != null && TextUtils.equals(info.title, appLabel);
// Create and add test session
mSessionId = createSession(appLabel, Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8));
// Verify promise icon is added
waitForLauncherCondition("Test Promise App not found on workspace", launcher ->
launcher.getWorkspace().getFirstMatch(findPromiseApp) != null);
// Remove session
mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
mSessionId = -1;
// Verify promise icon is removed
waitForLauncherCondition("Test Promise App not removed from workspace", launcher ->
launcher.getWorkspace().getFirstMatch(findPromiseApp) == null);
}
@Test
public void testPromiseIcon_notAddedFromIneligibleSession() throws Throwable {
final String appLabel = "Test Promise App " + UUID.randomUUID().toString();
final Workspace.ItemOperator findPromiseApp = (info, view) ->
info != null && TextUtils.equals(info.title, appLabel);
// Create and add test session without icon or label
mSessionId = createSession(null, null);
// Sleep for duration of animation if a view was to be added + some buffer time.
Thread.sleep(Launcher.NEW_APPS_PAGE_MOVE_DELAY + Launcher.NEW_APPS_ANIMATION_DELAY + 500);
// Verify promise icon is not added
waitForLauncherCondition("Test Promise App not found on workspace", launcher ->
launcher.getWorkspace().getFirstMatch(findPromiseApp) == null);
}
}
@@ -24,7 +24,6 @@ import static org.junit.Assert.assertTrue;
import static java.lang.System.exit;
import android.app.Instrumentation;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -112,6 +111,7 @@ public abstract class AbstractLauncherUiTest {
launcher ->
checkLauncherIntegrity(launcher, containerType)));
}
mLauncher.enableDebugTracing();
}
protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
@@ -187,14 +187,6 @@ public abstract class AbstractLauncherUiTest {
}
}
protected void lockRotation(boolean naturalOrientation) throws RemoteException {
if (naturalOrientation) {
mDevice.setOrientationNatural();
} else {
mDevice.setOrientationRight();
}
}
protected void clearLauncherData() throws IOException, InterruptedException {
if (TestHelpers.isInLauncherProcess()) {
LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
@@ -202,6 +194,7 @@ public abstract class AbstractLauncherUiTest {
resetLoaderState();
} else {
clearPackageData(mDevice.getLauncherPackageName());
mLauncher.enableDebugTracing();
}
}
@@ -273,6 +266,12 @@ public abstract class AbstractLauncherUiTest {
waitForLauncherCondition(message, condition, DEFAULT_ACTIVITY_TIMEOUT);
}
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
// flakiness.
protected <T> T getOnceNotNull(String message, Function<Launcher, T> f) {
return getOnceNotNull(message, f, DEFAULT_ACTIVITY_TIMEOUT);
}
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
// flakiness.
protected void waitForLauncherCondition(
@@ -281,6 +280,20 @@ public abstract class AbstractLauncherUiTest {
Wait.atMost(message, () -> getFromLauncher(condition), timeout);
}
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
// flakiness.
protected <T> T getOnceNotNull(String message, Function<Launcher, T> f, long timeout) {
if (!TestHelpers.isInLauncherProcess()) return null;
final Object[] output = new Object[1];
Wait.atMost(message, () -> {
final Object fromLauncher = getFromLauncher(f);
output[0] = fromLauncher;
return fromLauncher != null;
}, timeout);
return (T) output[0];
}
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
// flakiness.
protected void waitForLauncherCondition(
@@ -44,8 +44,11 @@ class PortraitLandscapeRunner implements TestRule {
} finally {
mTest.mDevice.setOrientationNatural();
mTest.executeOnLauncher(launcher ->
launcher.getRotationHelper().forceAllowRotationForTesting(
false));
{
if (launcher != null) {
launcher.getRotationHelper().forceAllowRotationForTesting(false);
}
});
mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
}
}
@@ -173,7 +173,6 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
@Test
public void testWorkspace() throws Exception {
mLauncher.enableDebugTracing();
final Workspace workspace = mLauncher.getWorkspace();
// Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
@@ -209,7 +208,6 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
// Test starting a workspace app.
final AppIcon app = workspace.getWorkspaceAppIcon("Chrome");
assertNotNull("No Chrome app in workspace", app);
mLauncher.disableDebugTracing();
}
public static void runIconLaunchFromAllAppsTest(AbstractLauncherUiTest test, AllApps allApps) {
@@ -300,7 +298,6 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
@Test
@PortraitLandscape
public void testDragAppIcon() throws Throwable {
mLauncher.enableDebugTracing();
// 1. Open all apps and wait for load complete.
// 2. Drag icon to homescreen.
// 3. Verify that the icon works on homescreen.
@@ -317,13 +314,11 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
"Launcher activity is the top activity; expecting another activity to be the top "
+ "one",
isInBackground(launcher)));
mLauncher.disableDebugTracing();
}
@Test
@PortraitLandscape
public void testDragShortcut() throws Throwable {
mLauncher.enableDebugTracing();
// 1. Open all apps and wait for load complete.
// 2. Find the app and long press it to show shortcuts.
// 3. Press icon center until shortcuts appear
@@ -343,7 +338,6 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
} finally {
allApps.unfreeze();
}
mLauncher.disableDebugTracing();
}
public static String getAppPackageName() {
@@ -69,34 +69,22 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest {
}
@Test
@PortraitLandscape
public void testWidgetConfig() throws Throwable {
runTest(false, true);
}
@Test
@Ignore // b/121280703
public void testWidgetConfig_rotate() throws Throwable {
runTest(true, true);
runTest(true);
}
@Test
@PortraitLandscape
public void testConfigCancelled() throws Throwable {
runTest(false, false);
runTest(false);
}
@Test
@Ignore // b/121280703
public void testConfigCancelled_rotate() throws Throwable {
runTest(true, false);
}
/**
* @param rotateConfig should the config screen be rotated
* @param acceptConfig accept the config activity
*/
private void runTest(boolean rotateConfig, boolean acceptConfig) throws Throwable {
lockRotation(true);
private void runTest(boolean acceptConfig) throws Throwable {
clearHomescreen();
mDevice.pressHome();
@@ -110,13 +98,6 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest {
// Widget id for which the config activity was opened
mWidgetId = monitor.getWidgetId();
if (rotateConfig) {
// Rotate the screen and verify that the config activity is recreated
monitor = new WidgetConfigStartupMonitor();
lockRotation(false);
assertEquals(mWidgetId, monitor.getWidgetId());
}
// Verify that the widget id is valid and bound
assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
@@ -15,6 +15,9 @@
*/
package com.android.launcher3.ui.widget;
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import androidx.test.filters.LargeTest;
@@ -22,6 +25,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.tapl.Widget;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.rule.ShellCommandRule;
@@ -41,19 +45,8 @@ public class AddWidgetTest extends AbstractLauncherUiTest {
@Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
@Test
public void testDragIcon_portrait() throws Throwable {
lockRotation(true);
performTest();
}
@Test
@Ignore // b/121280703
public void testDragIcon_landscape() throws Throwable {
lockRotation(false);
performTest();
}
private void performTest() throws Throwable {
@PortraitLandscape
public void testDragIcon() throws Throwable {
clearHomescreen();
mDevice.pressHome();
@@ -70,5 +63,10 @@ public class AddWidgetTest extends AbstractLauncherUiTest {
(info, view) -> info instanceof LauncherAppWidgetInfo &&
((LauncherAppWidgetInfo) info).providerName.getClassName().equals(
widgetInfo.provider.getClassName())).call());
final Widget widget = mLauncher.getWorkspace().tryGetWidget(widgetInfo.label,
DEFAULT_UI_TIMEOUT);
assertNotNull("Widget not found on the workspace", widget);
widget.launch(getAppPackageName());
}
}
@@ -128,8 +128,6 @@ public class RequestPinItemTest extends AbstractLauncherUiTest {
if (!Utilities.ATLEAST_OREO) {
return;
}
lockRotation(true);
clearHomescreen();
mDevice.pressHome();
@@ -368,7 +368,7 @@ public final class LauncherInstrumentation {
}
}
private void assertEquals(String message, String expected, String actual) {
void assertEquals(String message, String expected, String actual) {
if (!TextUtils.equals(expected, actual)) {
fail(message + " expected: '" + expected + "' but was: '" + actual + "'");
}
@@ -763,8 +763,7 @@ public final class LauncherInstrumentation {
final Bundle parcel = (Bundle) executeAndWaitForEvent(
() -> linearGesture(startX, startY, endX, endY, steps),
event -> TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName()),
"Swipe failed to receive an event for the swipe end: " + startX + ", " + startY
+ ", " + endX + ", " + endY);
"Swipe failed to receive an event for the swipe end");
assertEquals("Swipe switched launcher to a wrong state;",
TestProtocol.stateOrdinalToString(expectedState),
TestProtocol.stateOrdinalToString(parcel.getInt(TestProtocol.STATE_FIELD)));
@@ -958,8 +957,8 @@ public final class LauncherInstrumentation {
getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
}
public long getAllocatedMemory() {
return getTestInfo(TestProtocol.REQUEST_ALLOCATED_MEMORY).
getLong(TestProtocol.TEST_INFO_RESPONSE_FIELD);
public int getTotalPssKb() {
return getTestInfo(TestProtocol.REQUEST_TOTAL_PSS_KB).
getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
}
@@ -19,6 +19,7 @@ package com.android.launcher3.tapl;
import static org.junit.Assert.fail;
import android.graphics.Point;
import android.graphics.Rect;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
@@ -31,7 +32,8 @@ import com.android.launcher3.ResourceUtils;
* All widgets container.
*/
public final class Widgets extends LauncherInstrumentation.VisibleContainer {
private static final int FLING_SPEED = 1500;
private static final Rect MARGINS = new Rect(100, 100, 100, 100);
private static final int FLING_STEPS = 10;
Widgets(LauncherInstrumentation launcher) {
super(launcher);
@@ -46,11 +48,7 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
"want to fling forward in widgets")) {
LauncherInstrumentation.log("Widgets.flingForward enter");
final UiObject2 widgetsContainer = verifyActiveContainer();
widgetsContainer.setGestureMargins(0, 0, 0,
ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
mLauncher.getResources()) + 1);
widgetsContainer.fling(Direction.DOWN,
(int) (FLING_SPEED * mLauncher.getDisplayDensity()));
mLauncher.scroll(widgetsContainer, Direction.DOWN, 1f, MARGINS, FLING_STEPS);
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) {
verifyActiveContainer();
}
@@ -66,10 +64,7 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
"want to fling backwards in widgets")) {
LauncherInstrumentation.log("Widgets.flingBackward enter");
final UiObject2 widgetsContainer = verifyActiveContainer();
widgetsContainer.setGestureMargin(100);
widgetsContainer.fling(Direction.UP,
(int) (FLING_SPEED * mLauncher.getDisplayDensity()));
mLauncher.waitForIdle();
mLauncher.scroll(widgetsContainer, Direction.UP, 1f, MARGINS, FLING_STEPS);
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) {
verifyActiveContainer();
}
@@ -82,7 +77,7 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
return LauncherInstrumentation.ContainerType.WIDGETS;
}
public Widget getWidget(String label) {
public Widget getWidget(String labelText) {
final int margin = ResourceUtils.getNavbarSize(
ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
final UiObject2 widgetsContainer = verifyActiveContainer();
@@ -91,17 +86,24 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer {
final Point displaySize = mLauncher.getRealDisplaySize();
int i = 0;
final BySelector selector = By.
clazz("com.android.launcher3.widget.WidgetCell").
hasDescendant(By.text(label));
final BySelector selector = By.clazz("android.widget.TextView").text(labelText);
for (; ; ) {
final UiObject2 widget = mLauncher.tryWaitForLauncherObject(selector, 300);
if (widget != null && widget.getVisibleBounds().bottom <= displaySize.y - margin) {
return new Widget(mLauncher, widget);
final UiObject2 label = mLauncher.tryWaitForLauncherObject(selector, 300);
if (label != null) {
final UiObject2 widget = label.getParent().getParent();
mLauncher.assertEquals(
"View is not WidgetCell",
"com.android.launcher3.widget.WidgetCell",
widget.getClassName());
if (widget.getVisibleBounds().bottom <= displaySize.y - margin) {
return new Widget(mLauncher, widget);
}
}
if (++i > 40) fail("Too many attempts");
widgetsContainer.scroll(Direction.DOWN, 1f);
mLauncher.scroll(widgetsContainer, Direction.DOWN, 0.7f, MARGINS, 50);
}
}
}
@@ -21,6 +21,7 @@ import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
import static junit.framework.TestCase.assertTrue;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.SystemClock;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -40,6 +41,7 @@ public final class Workspace extends Home {
private static final float FLING_SPEED =
LauncherInstrumentation.isAvd() ? 1500.0F : 3500.0F;
private static final int DRAG_DURACTION = 2000;
private static final int FLING_STEPS = 10;
private final UiObject2 mHotseat;
Workspace(LauncherInstrumentation launcher) {
@@ -180,9 +182,9 @@ public final class Workspace extends Home {
*/
public void flingForward() {
final UiObject2 workspace = verifyActiveContainer();
workspace.setGestureMargins(0, 0, mLauncher.getEdgeSensitivityWidth(), 0);
workspace.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
mLauncher.waitForIdle();
mLauncher.scroll(workspace, Direction.RIGHT, 1f,
new Rect(0, 0, mLauncher.getEdgeSensitivityWidth(), 0),
FLING_STEPS);
verifyActiveContainer();
}
@@ -192,9 +194,9 @@ public final class Workspace extends Home {
*/
public void flingBackward() {
final UiObject2 workspace = verifyActiveContainer();
workspace.setGestureMargins(mLauncher.getEdgeSensitivityWidth(), 0, 0, 0);
workspace.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
mLauncher.waitForIdle();
mLauncher.scroll(workspace, Direction.LEFT, 1f,
new Rect(mLauncher.getEdgeSensitivityWidth(), 0, 0, 0),
FLING_STEPS);
verifyActiveContainer();
}