Merge "Allow Workspace Scrim to be colored per state" into sc-dev
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 5b1b59b..fb67645 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -79,6 +79,7 @@
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.views.FloatingIconView;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.quickstep.RemoteAnimationTargets;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskViewUtils;
@@ -86,6 +87,7 @@
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.views.FloatingWidgetView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityOptionsCompat;
@@ -100,7 +102,9 @@
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.wm.shell.startingsurface.IStartingWindowListener;
+import java.util.ArrayList;
import java.util.LinkedHashMap;
+import java.util.List;
/**
* Manages the opening and closing app transitions from Launcher
@@ -160,6 +164,9 @@
private static final int MAX_NUM_TASKS = 5;
+ // Cross-fade duration between App Widget and App
+ private static final int WIDGET_CROSSFADE_DURATION_MILLIS = 125;
+
protected final BaseQuickstepLauncher mLauncher;
private final DragLayer mDragLayer;
@@ -349,6 +356,29 @@
}
}
+ private void composeWidgetLaunchAnimator(
+ @NonNull AnimatorSet anim,
+ @NonNull LauncherAppWidgetHostView v,
+ @NonNull RemoteAnimationTargetCompat[] appTargets,
+ @NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
+ @NonNull RemoteAnimationTargetCompat[] nonAppTargets) {
+ mLauncher.getStateManager().setCurrentAnimation(anim);
+
+ Rect windowTargetBounds = getWindowTargetBounds(appTargets, getRotationChange(appTargets));
+ anim.play(getOpeningWindowAnimatorsForWidget(v, appTargets, wallpaperTargets, nonAppTargets,
+ windowTargetBounds, areAllTargetsTranslucent(appTargets)));
+
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mLauncher.addOnResumeCallback(() ->
+ ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH,
+ mLauncher.getStateManager().getState().getDepth(
+ mLauncher)).start());
+ }
+ });
+ }
+
/**
* Return the window bounds of the opening target.
* In multiwindow mode, we need to get the final size of the opening app window target to help
@@ -457,32 +487,29 @@
alpha.setInterpolator(LINEAR);
launcherAnimator.play(alpha);
+ List<View> viewsToAnimate = new ArrayList<>();
+
Workspace workspace = mLauncher.getWorkspace();
- View currentPage = ((CellLayout) workspace.getChildAt(workspace.getCurrentPage()))
- .getShortcutsAndWidgets();
- View hotseat = mLauncher.getHotseat();
- View qsb = mLauncher.findViewById(R.id.search_container_all_apps);
+ workspace.getVisiblePages().forEach(
+ view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets()));
- currentPage.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- qsb.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ viewsToAnimate.add(mLauncher.getHotseat());
+ // Add QSB
+ viewsToAnimate.add(mLauncher.findViewById(R.id.search_container_all_apps));
- launcherAnimator.play(ObjectAnimator.ofFloat(currentPage, View.TRANSLATION_Y, trans));
- launcherAnimator.play(ObjectAnimator.ofFloat(hotseat, View.TRANSLATION_Y, trans));
- launcherAnimator.play(ObjectAnimator.ofFloat(qsb, View.TRANSLATION_Y, trans));
+ viewsToAnimate.forEach(view -> {
+ view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ launcherAnimator.play(ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, trans));
+ });
// Pause page indicator animations as they lead to layer trashing.
mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
endListener = () -> {
- currentPage.setTranslationY(0);
- hotseat.setTranslationY(0);
- qsb.setTranslationY(0);
-
- currentPage.setLayerType(View.LAYER_TYPE_NONE, null);
- hotseat.setLayerType(View.LAYER_TYPE_NONE, null);
- qsb.setLayerType(View.LAYER_TYPE_NONE, null);
-
+ viewsToAnimate.forEach(view -> {
+ view.setTranslationY(0);
+ view.setLayerType(View.LAYER_TYPE_NONE, null);
+ });
mDragLayerAlpha.setValue(1f);
mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
};
@@ -737,6 +764,112 @@
}
});
+ animatorSet.playTogether(appAnimator, getBackgroundAnimator(appTargets));
+ return animatorSet;
+ }
+
+ private Animator getOpeningWindowAnimatorsForWidget(LauncherAppWidgetHostView v,
+ RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonAppTargets, Rect windowTargetBounds,
+ boolean appTargetsAreTranslucent) {
+ final RectF widgetBackgroundBounds = new RectF();
+ final Rect appWindowCrop = new Rect();
+ final Matrix matrix = new Matrix();
+
+ final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
+ ? 0 : getWindowCornerRadius(mLauncher.getResources());
+ final FloatingWidgetView floatingView = FloatingWidgetView.getFloatingWidgetView(mLauncher,
+ v, widgetBackgroundBounds, windowTargetBounds, finalWindowRadius);
+ final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources())
+ ? floatingView.getInitialCornerRadius() : 0;
+
+ RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets,
+ wallpaperTargets, nonAppTargets, MODE_OPENING);
+ SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(floatingView);
+ openingTargets.addReleaseCheck(surfaceApplier);
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
+ appAnimator.setDuration(APP_LAUNCH_DURATION);
+ appAnimator.setInterpolator(LINEAR);
+ appAnimator.addListener(floatingView);
+ appAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ openingTargets.release();
+ }
+ });
+ floatingView.setFastFinishRunnable(animatorSet::end);
+
+ appAnimator.addUpdateListener(new MultiValueUpdateListener() {
+ float mAppWindowScale = 1;
+ final FloatProp mWidgetForegroundAlpha = new FloatProp(1 /* start */,
+ 0 /* end */, 0 /* delay */,
+ WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR);
+ final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0 /* start */,
+ 1 /* end */, 0 /* delay */, 75 /* duration */, LINEAR);
+ final FloatProp mPreviewAlpha = new FloatProp(0 /* start */, 1 /* end */,
+ WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* delay */,
+ WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR);
+ final FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius,
+ 0 /* start */, RADIUS_DURATION, LINEAR);
+ final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, 0, RADIUS_DURATION, LINEAR);
+
+ // Window & widget background positioning bounds
+ final FloatProp mDx = new FloatProp(widgetBackgroundBounds.centerX(),
+ windowTargetBounds.centerX(), 0 /* delay */, APP_LAUNCH_CURVED_DURATION,
+ EXAGGERATED_EASE);
+ final FloatProp mDy = new FloatProp(widgetBackgroundBounds.centerY(),
+ windowTargetBounds.centerY(), 0 /* delay */, APP_LAUNCH_DURATION,
+ EXAGGERATED_EASE);
+ final FloatProp mWidth = new FloatProp(widgetBackgroundBounds.width(),
+ windowTargetBounds.width(), 0 /* delay */, APP_LAUNCH_DURATION,
+ EXAGGERATED_EASE);
+ final FloatProp mHeight = new FloatProp(widgetBackgroundBounds.height(),
+ windowTargetBounds.height(), 0 /* delay */, APP_LAUNCH_DURATION,
+ EXAGGERATED_EASE);
+
+ @Override
+ public void onUpdate(float percent) {
+ widgetBackgroundBounds.set(mDx.value - mWidth.value / 2f,
+ mDy.value - mHeight.value / 2f, mDx.value + mWidth.value / 2f,
+ mDy.value + mHeight.value / 2f);
+ // Set app window scaling factor to match widget background width
+ mAppWindowScale = widgetBackgroundBounds.width() / windowTargetBounds.width();
+ // Crop scaled app window to match widget
+ appWindowCrop.set(0 /* left */, 0 /* top */,
+ Math.round(windowTargetBounds.width()) /* right */,
+ Math.round(widgetBackgroundBounds.height() / mAppWindowScale) /* bottom */);
+ matrix.setTranslate(widgetBackgroundBounds.left, widgetBackgroundBounds.top);
+ matrix.postScale(mAppWindowScale, mAppWindowScale, widgetBackgroundBounds.left,
+ widgetBackgroundBounds.top);
+
+ SurfaceParams[] params = new SurfaceParams[appTargets.length];
+ float floatingViewAlpha = appTargetsAreTranslucent ? 1 - mPreviewAlpha.value : 1;
+ for (int i = appTargets.length - 1; i >= 0; i--) {
+ RemoteAnimationTargetCompat target = appTargets[i];
+ SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash);
+ if (target.mode == MODE_OPENING) {
+ floatingView.update(widgetBackgroundBounds, floatingViewAlpha,
+ mWidgetForegroundAlpha.value, mWidgetFallbackBackgroundAlpha.value,
+ mCornerRadiusProgress.value);
+ builder.withMatrix(matrix)
+ .withWindowCrop(appWindowCrop)
+ .withAlpha(mPreviewAlpha.value)
+ .withCornerRadius(mWindowRadius.value / mAppWindowScale);
+ }
+ params[i] = builder.build();
+ }
+ surfaceApplier.scheduleApply(params);
+ }
+ });
+
+ animatorSet.playTogether(appAnimator, getBackgroundAnimator(appTargets));
+ return animatorSet;
+ }
+
+ private ObjectAnimator getBackgroundAnimator(RemoteAnimationTargetCompat[] appTargets) {
// When launching an app from overview that doesn't map to a task, we still want to just
// blur the wallpaper instead of the launcher surface as well
boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW;
@@ -754,9 +887,7 @@
}
});
}
-
- animatorSet.playTogether(appAnimator, backgroundRadiusAnim);
- return animatorSet;
+ return backgroundRadiusAnim;
}
/**
@@ -1120,9 +1251,13 @@
boolean launcherClosing =
launcherIsATargetWithMode(appTargets, MODE_CLOSING);
+ final boolean launchingFromWidget = mV instanceof LauncherAppWidgetHostView;
final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets);
final boolean launchingFromTaskbar = mLauncher.isViewInTaskbar(mV);
- if (launchingFromRecents) {
+ if (launchingFromWidget) {
+ composeWidgetLaunchAnimator(anim, (LauncherAppWidgetHostView) mV, appTargets,
+ wallpaperTargets, nonAppTargets);
+ } else if (launchingFromRecents) {
composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,
launcherClosing);
} else if (launchingFromTaskbar) {
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 2a6e478..c47300c 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -92,7 +92,7 @@
};
private static final String TAG = "OrientationTouchTransformer";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private static final int QUICKSTEP_ROTATION_UNINITIALIZED = -1;
@@ -163,6 +163,10 @@
void setNavigationMode(SysUINavigationMode.Mode newMode, Info info,
Resources newRes) {
+ if (DEBUG) {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "setNavigationMode new: " + newMode
+ + " oldMode: " + mMode + " " + this);
+ }
if (mMode == newMode) {
return;
}
@@ -254,10 +258,18 @@
mCurrentDisplay = new CurrentDisplay(region.realSize, region.rotation);
OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplay);
+ if (DEBUG) {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "cached region: " + regionToKeep
+ + " mCurrentDisplay: " + mCurrentDisplay + " " + this);
+ }
if (regionToKeep == null) {
regionToKeep = createRegionForDisplay(region);
}
mSwipeTouchRegions.clear();
+ if (DEBUG) {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "adding region: " + regionToKeep
+ + " mCurrentDisplay: " + mCurrentDisplay + " " + this);
+ }
mSwipeTouchRegions.put(mCurrentDisplay, regionToKeep);
updateAssistantRegions(regionToKeep);
}
@@ -273,7 +285,8 @@
private OrientationRectF createRegionForDisplay(Info display) {
if (DEBUG) {
- Log.d(TAG, "creating rotation region for: " + mCurrentDisplay.rotation);
+ Log.d(TAG, "creating rotation region for: " + mCurrentDisplay.rotation
+ + " with mode: " + mMode + " displayRotation: " + display.rotation);
}
Point size = display.realSize;
@@ -287,14 +300,19 @@
} else {
mAssistantLeftRegion.setEmpty();
mAssistantRightRegion.setEmpty();
+ int navbarSize = getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
+ if (DEBUG) {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "else case mode: " + mMode
+ + " getNavbarSize: " + navbarSize + " rotation: " + rotation + " " + this);
+ }
switch (rotation) {
case Surface.ROTATION_90:
orientationRectF.left = orientationRectF.right
- - getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
+ - navbarSize;
break;
case Surface.ROTATION_270:
orientationRectF.right = orientationRectF.left
- + getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
+ + navbarSize;
break;
default:
orientationRectF.top = orientationRectF.bottom - touchHeight;
@@ -339,7 +357,7 @@
boolean touchInValidSwipeRegions(float x, float y) {
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "touchInValidSwipeRegions " + x + "," + y + " in "
- + mLastRectTouched);
+ + mLastRectTouched + " this: " + this);
}
if (mLastRectTouched != null) {
return mLastRectTouched.contains(x, y);
@@ -462,7 +480,8 @@
if (DEBUG) {
Log.d(TAG, "Transforming rotation due to forceTransform, "
+ "mCurrentRotation: " + mCurrentDisplay.rotation
- + "mRotation: " + mRotation);
+ + "mRotation: " + mRotation
+ + " this: " + this);
}
event.transform(mTmpMatrix);
return true;
@@ -473,9 +492,10 @@
if (DEBUG) {
Log.d(TAG, "original: " + event.getX() + ", " + event.getY()
- + " new: " + mTmpPoint[0] + ", " + mTmpPoint[1]
- + " rect: " + this + " forceTransform: " + forceTransform
- + " contains: " + contains(mTmpPoint[0], mTmpPoint[1]));
+ + " new: " + mTmpPoint[0] + ", " + mTmpPoint[1]
+ + " rect: " + this + " forceTransform: " + forceTransform
+ + " contains: " + contains(mTmpPoint[0], mTmpPoint[1])
+ + " this: " + this);
}
if (contains(mTmpPoint[0], mTmpPoint[1])) {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index b4f1330..ef09957 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -54,6 +54,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
@@ -61,6 +62,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
@@ -127,14 +129,27 @@
private boolean mIsUserSetupComplete;
public RecentsAnimationDeviceState(Context context) {
+ this(context, false);
+ }
+
+ /**
+ * @param isInstanceForTouches {@code true} if this is the persistent instance being used for
+ * gesture touch handling
+ */
+ public RecentsAnimationDeviceState(Context context, boolean isInstanceForTouches) {
mContext = context;
mDisplayController = DisplayController.INSTANCE.get(context);
mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
mDisplayId = mDisplayController.getInfo().id;
mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
runOnDestroy(() -> mDisplayController.removeChangeListener(this));
- mRotationTouchHelper = new RotationTouchHelper(context, mDisplayController);
- runOnDestroy(mRotationTouchHelper::destroy);
+ mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
+ if (isInstanceForTouches) {
+ // rotationTouchHelper doesn't get initialized after being destroyed, so only destroy
+ // if primary TouchInteractionService instance needs to be destroyed.
+ mRotationTouchHelper.init();
+ runOnDestroy(mRotationTouchHelper::destroy);
+ }
// Register for user unlocked if necessary
mIsUserUnlocked = context.getSystemService(UserManager.class)
@@ -214,6 +229,7 @@
* Cleans up all the registered listeners and receivers.
*/
public void destroy() {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "destroying RADS", new Throwable());
for (Runnable r : mOnDestroyActions) {
r.run();
}
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index f4688a1..fd0de42 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
@@ -31,6 +32,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
@@ -43,10 +45,13 @@
SysUINavigationMode.NavigationModeChangeListener,
DisplayInfoChangeListener {
- private final OrientationTouchTransformer mOrientationTouchTransformer;
- private final DisplayController mDisplayController;
- private final SysUINavigationMode mSysUiNavMode;
- private final int mDisplayId;
+ public static final MainThreadInitializedObject<RotationTouchHelper> INSTANCE =
+ new MainThreadInitializedObject<>(RotationTouchHelper::new);
+
+ private OrientationTouchTransformer mOrientationTouchTransformer;
+ private DisplayController mDisplayController;
+ private SysUINavigationMode mSysUiNavMode;
+ private int mDisplayId;
private int mDisplayRotation;
private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
@@ -117,25 +122,46 @@
*/
private boolean mInOverview;
private boolean mTaskListFrozen;
-
-
private final Context mContext;
- public RotationTouchHelper(Context context, DisplayController displayController) {
+ /**
+ * Keeps track of whether destroy has been called for this instance. Mainly used for TAPL tests
+ * where multiple instances of RotationTouchHelper are being created. b/177316094
+ */
+ private boolean mNeedsInit = true;
+
+ private RotationTouchHelper(Context context) {
mContext = context;
- mDisplayController = displayController;
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "RotationTouchHelper ctor init? " + mNeedsInit
+ + " " + this);
+ if (mNeedsInit) {
+ init();
+ }
+ }
+
+ public void init() {
+ if (!mNeedsInit) {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "Did not need init? " + " " + this);
+ return;
+ }
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "RotationTouchHelper init() " + this,
+ new Throwable());
+ mDisplayController = DisplayController.INSTANCE.get(mContext);
Resources resources = mContext.getResources();
- mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
+ mSysUiNavMode = SysUINavigationMode.INSTANCE.get(mContext);
mDisplayId = mDisplayController.getInfo().id;
mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
() -> QuickStepContract.getWindowCornerRadius(resources));
// Register for navigation mode changes
- onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
+ SysUINavigationMode.Mode newMode = mSysUiNavMode.addModeChangeListener(this);
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "AddedModeChangeListener: " + this +
+ " currentMode: " + newMode);
+ onNavigationModeChanged(newMode);
runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
- mOrientationListener = new OrientationEventListener(context) {
+ mOrientationListener = new OrientationEventListener(mContext) {
@Override
public void onOrientationChanged(int degrees) {
int newRotation = RecentsOrientedState.getRotationForUserDegreesRotated(degrees,
@@ -154,6 +180,7 @@
}
}
};
+ mNeedsInit = false;
}
private void setupOrientationSwipeHandler() {
@@ -176,9 +203,11 @@
* Cleans up all the registered listeners and receivers.
*/
public void destroy() {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "destroying " + this);
for (Runnable r : mOnDestroyActions) {
r.run();
}
+ mNeedsInit = true;
}
public boolean isTaskListFrozen() {
@@ -223,6 +252,7 @@
@Override
public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "nav mode changed: " + newMode);
mDisplayController.removeChangeListener(this);
mDisplayController.addChangeListener(this);
onDisplayInfoChanged(mContext, mDisplayController.getInfo(), CHANGE_ALL);
@@ -374,4 +404,8 @@
pw.println(" displayRotation=" + getDisplayRotation());
mOrientationTouchTransformer.dump(pw);
}
+
+ public OrientationTouchTransformer getOrientationTouchTransformer() {
+ return mOrientationTouchTransformer;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index b6dad2d..c87cd17 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -86,6 +86,7 @@
@UiThread
public RecentsAnimationCallbacks startRecentsAnimation(GestureState gestureState,
Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) {
+ Log.d("b/186444448", "startRecentsAnimation");
// Notify if recents animation is still running
if (mController != null) {
String msg = "New recents animation started before old animation completed";
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index f3fe0b4..c74f53b 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -300,7 +300,9 @@
// Everything else should be initialized in onUserUnlocked() below.
mMainChoreographer = Choreographer.getInstance();
mAM = ActivityManagerWrapper.getInstance();
- mDeviceState = new RecentsAnimationDeviceState(this);
+ mDeviceState = new RecentsAnimationDeviceState(this, true);
+ Log.d(TestProtocol.NO_SWIPE_TO_HOME, "RADS OTT instance: " +
+ mDeviceState.getRotationTouchHelper().getOrientationTouchTransformer());
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
new file mode 100644
index 0000000..f74aa55
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2021 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.views;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Outline;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.widget.RemoteViews.RemoteViewOutlineProvider;
+
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.RoundedCornerEnforcement;
+
+import java.util.stream.IntStream;
+
+/**
+ * Mimics the appearance of the background view of a {@link LauncherAppWidgetHostView} through a
+ * an App Widget activity launch animation.
+ */
+@TargetApi(Build.VERSION_CODES.S)
+final class FloatingWidgetBackgroundView extends View {
+ private final ColorDrawable mFallbackDrawable = new ColorDrawable();
+ private final DrawableProperties mForegroundProperties = new DrawableProperties();
+ private final DrawableProperties mBackgroundProperties = new DrawableProperties();
+
+ private Drawable mOriginalForeground;
+ private Drawable mOriginalBackground;
+ private float mFinalRadius;
+ private float mInitialOutlineRadius;
+ private float mOutlineRadius;
+ private boolean mIsUsingFallback;
+ private View mSourceView;
+
+ FloatingWidgetBackgroundView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius);
+ }
+ });
+ setClipToOutline(true);
+ }
+
+ void init(LauncherAppWidgetHostView hostView, View backgroundView, float finalRadius) {
+ mFinalRadius = finalRadius;
+ mSourceView = backgroundView;
+ mInitialOutlineRadius = getOutlineRadius(hostView, backgroundView);
+ mIsUsingFallback = false;
+ if (isSupportedDrawable(backgroundView.getForeground())) {
+ mOriginalForeground = backgroundView.getForeground();
+ mForegroundProperties.init(
+ mOriginalForeground.getConstantState().newDrawable().mutate());
+ setForeground(mForegroundProperties.mDrawable);
+ mSourceView.setForeground(null);
+ }
+ if (isSupportedDrawable(backgroundView.getBackground())) {
+ mOriginalBackground = backgroundView.getBackground();
+ mBackgroundProperties.init(
+ mOriginalBackground.getConstantState().newDrawable().mutate());
+ setBackground(mBackgroundProperties.mDrawable);
+ mSourceView.setBackground(null);
+ } else if (mOriginalForeground == null) {
+ mFallbackDrawable.setColor(Themes.getColorBackground(backgroundView.getContext()));
+ setBackground(mFallbackDrawable);
+ mIsUsingFallback = true;
+ }
+ }
+
+ /** Update the animated properties of the drawables. */
+ void update(float cornerRadiusProgress, float fallbackAlpha) {
+ if (isUninitialized()) return;
+ mOutlineRadius = mInitialOutlineRadius + (mFinalRadius - mInitialOutlineRadius)
+ * cornerRadiusProgress;
+ mForegroundProperties.updateDrawable(mFinalRadius, cornerRadiusProgress);
+ mBackgroundProperties.updateDrawable(mFinalRadius, cornerRadiusProgress);
+ setAlpha(mIsUsingFallback ? fallbackAlpha : 1f);
+ }
+
+ /** Restores the drawables to the source view. */
+ void finish() {
+ if (isUninitialized()) return;
+ mSourceView.setForeground(mOriginalForeground);
+ mSourceView.setBackground(mOriginalBackground);
+ }
+
+ void recycle() {
+ mSourceView = null;
+ mOriginalForeground = null;
+ mOriginalBackground = null;
+ mOutlineRadius = 0;
+ mFinalRadius = 0;
+ setForeground(null);
+ setBackground(null);
+ }
+
+ /** Get the largest of drawable corner radii or background view outline radius. */
+ float getMaximumRadius() {
+ if (isUninitialized()) return 0;
+ return Math.max(mInitialOutlineRadius, Math.max(getMaxRadius(mOriginalForeground),
+ getMaxRadius(mOriginalBackground)));
+ }
+
+ private boolean isUninitialized() {
+ return mSourceView == null;
+ }
+
+ /** Returns the maximum corner radius of {@param drawable}. */
+ private static float getMaxRadius(Drawable drawable) {
+ if (!(drawable instanceof GradientDrawable)) return 0;
+ float[] cornerRadii = ((GradientDrawable) drawable).getCornerRadii();
+ float cornerRadius = ((GradientDrawable) drawable).getCornerRadius();
+ double radiiMax = cornerRadii == null ? 0 : IntStream.range(0, cornerRadii.length)
+ .mapToDouble(i -> cornerRadii[i]).max().orElse(0);
+ return Math.max(cornerRadius, (float) radiiMax);
+ }
+
+ /** Returns whether the given drawable type is supported. */
+ private static boolean isSupportedDrawable(Drawable drawable) {
+ return drawable instanceof ColorDrawable || (drawable instanceof GradientDrawable
+ && ((GradientDrawable) drawable).getShape() == GradientDrawable.RECTANGLE);
+ }
+
+ /** Corner radius from source view's outline, or enforced view. */
+ private static float getOutlineRadius(LauncherAppWidgetHostView hostView, View v) {
+ if (RoundedCornerEnforcement.isRoundedCornerEnabled()
+ && hostView.hasEnforcedCornerRadius()) {
+ return hostView.getEnforcedCornerRadius();
+ } else if (v.getOutlineProvider() instanceof RemoteViewOutlineProvider
+ && v.getClipToOutline()) {
+ return ((RemoteViewOutlineProvider) v.getOutlineProvider()).getRadius();
+ }
+ return 0;
+ }
+
+ /** Stores and modifies a drawable's properties through an animation. */
+ private static class DrawableProperties {
+ private Drawable mDrawable;
+ private float mOriginalRadius;
+ private float[] mOriginalRadii;
+ private final float[] mTmpRadii = new float[8];
+
+ /** Store a drawable's animated properties. */
+ void init(Drawable drawable) {
+ mDrawable = drawable;
+ if (!(drawable instanceof GradientDrawable)) return;
+ mOriginalRadius = ((GradientDrawable) drawable).getCornerRadius();
+ mOriginalRadii = ((GradientDrawable) drawable).getCornerRadii();
+ }
+
+ /**
+ * Update the drawable for the given animation state.
+ *
+ * @param finalRadius the radius of each corner when {@param progress} is 1
+ * @param progress the linear progress of the corner radius from its original value to
+ * {@param finalRadius}
+ */
+ void updateDrawable(float finalRadius, float progress) {
+ if (!(mDrawable instanceof GradientDrawable)) return;
+ GradientDrawable d = (GradientDrawable) mDrawable;
+ if (mOriginalRadii != null) {
+ for (int i = 0; i < mOriginalRadii.length; i++) {
+ mTmpRadii[i] = mOriginalRadii[i] + (finalRadius - mOriginalRadii[i]) * progress;
+ }
+ d.setCornerRadii(mTmpRadii);
+ } else {
+ d.setCornerRadius(mOriginalRadius + (finalRadius - mOriginalRadius) * progress);
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
new file mode 100644
index 0000000..d23884c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2021 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.views;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.GhostView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.views.ListenerView;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.RoundedCornerEnforcement;
+
+/** A view that mimics an App Widget through a launch animation. */
+@TargetApi(Build.VERSION_CODES.S)
+public class FloatingWidgetView extends FrameLayout implements AnimatorListener {
+ private static final Matrix sTmpMatrix = new Matrix();
+
+ private final Launcher mLauncher;
+ private final ListenerView mListenerView;
+ private final FloatingWidgetBackgroundView mBackgroundView;
+ private final RectF mBackgroundOffset = new RectF();
+
+ private LauncherAppWidgetHostView mAppWidgetView;
+ private View mAppWidgetBackgroundView;
+ private RectF mBackgroundPosition;
+ private GhostView mForegroundOverlayView;
+
+ private Runnable mEndRunnable;
+ private Runnable mFastFinishRunnable;
+
+ public FloatingWidgetView(Context context) {
+ this(context, null);
+ }
+
+ public FloatingWidgetView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FloatingWidgetView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mLauncher = Launcher.getLauncher(context);
+ mListenerView = new ListenerView(context, attrs);
+ mBackgroundView = new FloatingWidgetBackgroundView(context, attrs, defStyleAttr);
+ addView(mBackgroundView);
+ setWillNotDraw(false);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ Runnable endRunnable = mEndRunnable;
+ mEndRunnable = null;
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animator) {
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {
+ }
+
+ /** Sets a runnable that is called after a call to {@link #fastFinish()}. */
+ public void setFastFinishRunnable(Runnable runnable) {
+ mFastFinishRunnable = runnable;
+ }
+
+ /** Callback at the end or early exit of the animation. */
+ public void fastFinish() {
+ if (isUninitialized()) return;
+ Runnable fastFinishRunnable = mFastFinishRunnable;
+ if (fastFinishRunnable != null) {
+ fastFinishRunnable.run();
+ }
+ Runnable endRunnable = mEndRunnable;
+ mEndRunnable = null;
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ }
+
+ private void init(DragLayer dragLayer, LauncherAppWidgetHostView originalView,
+ RectF widgetBackgroundPosition, Rect windowTargetBounds, float windowCornerRadius) {
+ mAppWidgetView = originalView;
+ mAppWidgetView.beginDeferringUpdates();
+ mBackgroundPosition = widgetBackgroundPosition;
+ mEndRunnable = () -> finish(dragLayer);
+
+ mAppWidgetBackgroundView = RoundedCornerEnforcement.findBackground(mAppWidgetView);
+ if (mAppWidgetBackgroundView == null) {
+ mAppWidgetBackgroundView = mAppWidgetView;
+ }
+
+ getRelativePosition(mAppWidgetBackgroundView, dragLayer, mBackgroundPosition);
+ getRelativePosition(mAppWidgetBackgroundView, mAppWidgetView, mBackgroundOffset);
+ mBackgroundView.init(mAppWidgetView, mAppWidgetBackgroundView, windowCornerRadius);
+ // Layout call before GhostView creation so that the overlaid view isn't clipped
+ layout(0, 0, windowTargetBounds.width(), windowTargetBounds.height());
+ mForegroundOverlayView = GhostView.addGhost(mAppWidgetView, this);
+ positionViews();
+
+ mListenerView.setListener(this::fastFinish);
+ dragLayer.addView(mListenerView);
+ }
+
+ /**
+ * Updates the position and opacity of the floating widget's components.
+ *
+ * @param backgroundPosition the new position of the widget's background relative to the
+ * {@link FloatingWidgetView}'s parent
+ * @param floatingWidgetAlpha the overall opacity of the {@link FloatingWidgetView}
+ * @param foregroundAlpha the opacity of the foreground layer
+ * @param fallbackBackgroundAlpha the opacity of the fallback background used when the App
+ * Widget doesn't have a background
+ * @param cornerRadiusProgress progress of the corner radius animation, where 0 is the
+ * original radius and 1 is the window radius
+ */
+ public void update(RectF backgroundPosition, float floatingWidgetAlpha, float foregroundAlpha,
+ float fallbackBackgroundAlpha, float cornerRadiusProgress) {
+ if (isUninitialized()) return;
+ setAlpha(floatingWidgetAlpha);
+ mBackgroundView.update(cornerRadiusProgress, fallbackBackgroundAlpha);
+ mAppWidgetView.setAlpha(foregroundAlpha);
+ mBackgroundPosition = backgroundPosition;
+ positionViews();
+ }
+
+ /** Sets the layout parameters of the floating view and its background view child. */
+ private void positionViews() {
+ LayoutParams layoutParams = (LayoutParams) getLayoutParams();
+ layoutParams.setMargins(0, 0, 0, 0);
+ setLayoutParams(layoutParams);
+
+ // FloatingWidgetView layout is forced LTR
+ mBackgroundView.setTranslationX(mBackgroundPosition.left);
+ mBackgroundView.setTranslationY(mBackgroundPosition.top);
+ LayoutParams backgroundParams = (LayoutParams) mBackgroundView.getLayoutParams();
+ backgroundParams.leftMargin = 0;
+ backgroundParams.topMargin = 0;
+ backgroundParams.width = (int) mBackgroundPosition.width();
+ backgroundParams.height = (int) mBackgroundPosition.height();
+ mBackgroundView.setLayoutParams(backgroundParams);
+
+ sTmpMatrix.reset();
+ float foregroundScale = mBackgroundPosition.width() / mAppWidgetBackgroundView.getWidth();
+ sTmpMatrix.setTranslate(-mBackgroundOffset.left - mAppWidgetView.getLeft(),
+ -mBackgroundOffset.top - mAppWidgetView.getTop());
+ sTmpMatrix.postScale(foregroundScale, foregroundScale);
+ sTmpMatrix.postTranslate(mBackgroundPosition.left, mBackgroundPosition.top);
+ mForegroundOverlayView.setMatrix(sTmpMatrix);
+ }
+
+ private void finish(DragLayer dragLayer) {
+ mAppWidgetView.setAlpha(1f);
+ GhostView.removeGhost(mAppWidgetView);
+ ((ViewGroup) dragLayer.getParent()).removeView(this);
+ dragLayer.removeView(mListenerView);
+ mBackgroundView.finish();
+ mAppWidgetView.endDeferringUpdates();
+ recycle();
+ mLauncher.getViewCache().recycleView(R.layout.floating_widget_view, this);
+ }
+
+ public float getInitialCornerRadius() {
+ return mBackgroundView.getMaximumRadius();
+ }
+
+ private boolean isUninitialized() {
+ return mForegroundOverlayView == null;
+ }
+
+ private void recycle() {
+ mEndRunnable = null;
+ mFastFinishRunnable = null;
+ mBackgroundPosition = null;
+ mListenerView.setListener(null);
+ mAppWidgetView = null;
+ mForegroundOverlayView = null;
+ mAppWidgetBackgroundView = null;
+ mBackgroundView.recycle();
+ }
+
+ /**
+ * Configures and returns a an instance of {@link FloatingWidgetView} matching the appearance of
+ * {@param originalView}.
+ *
+ * @param widgetBackgroundPosition a {@link RectF} that will be updated with the widget's
+ * background bounds
+ * @param windowTargetBounds the bounds of the window when launched
+ * @param windowCornerRadius the corner radius of the window
+ */
+ public static FloatingWidgetView getFloatingWidgetView(Launcher launcher,
+ LauncherAppWidgetHostView originalView, RectF widgetBackgroundPosition,
+ Rect windowTargetBounds, float windowCornerRadius) {
+ final DragLayer dragLayer = launcher.getDragLayer();
+ ViewGroup parent = (ViewGroup) dragLayer.getParent();
+ FloatingWidgetView floatingView =
+ launcher.getViewCache().getView(R.layout.floating_widget_view, launcher, parent);
+ floatingView.recycle();
+
+ floatingView.init(dragLayer, originalView, widgetBackgroundPosition, windowTargetBounds,
+ windowCornerRadius);
+ parent.addView(floatingView);
+ return floatingView;
+ }
+
+ private static void getRelativePosition(View descendant, View ancestor, RectF position) {
+ float[] points = new float[]{0, 0, descendant.getWidth(), descendant.getHeight()};
+ Utilities.getDescendantCoordRelativeToAncestor(descendant, ancestor, points,
+ false /* includeRootScroll */);
+ position.set(
+ Math.min(points[0], points[2]),
+ Math.min(points[1], points[3]),
+ Math.max(points[0], points[2]),
+ Math.max(points[1], points[3]));
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 41076f3..596f746 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1277,23 +1277,20 @@
}
float accumulatedTranslationX = 0;
- float[] fullscreenTranslations = new float[taskCount];
for (int i = 0; i < taskCount; i++) {
TaskView taskView = getTaskViewAt(i);
taskView.updateTaskSize();
- fullscreenTranslations[i] += accumulatedTranslationX;
+ taskView.getPrimaryFullscreenTranslationProperty().set(taskView,
+ accumulatedTranslationX);
+ taskView.getSecondaryFullscreenTranslationProperty().set(taskView, 0f);
// Compensate space caused by TaskView scaling.
float widthDiff =
taskView.getLayoutParams().width * (1 - taskView.getFullscreenScale());
// Compensate page spacing widening caused by RecentsView scaling.
widthDiff += mPageSpacing * (1 - 1 / mFullscreenScale);
- float fullscreenTranslationX = mIsRtl ? widthDiff : -widthDiff;
- accumulatedTranslationX += fullscreenTranslationX;
+ accumulatedTranslationX += mIsRtl ? widthDiff : -widthDiff;
}
- for (int i = 0; i < taskCount; i++) {
- getTaskViewAt(i).setFullscreenTranslationX(fullscreenTranslations[i]);
- }
mClearAllButton.setFullscreenTranslationPrimary(accumulatedTranslationX);
updateGridProperties();
@@ -1600,8 +1597,9 @@
* {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
*/
public void onSwipeUpAnimationSuccess() {
+ Log.d("b/186444448", "onSwipeUpAnimationSuccess");
if (getRunningTaskView() != null) {
- animateUpRunningTaskIconScale(0f);
+ animateUpRunningTaskIconScale();
}
setSwipeDownShouldLaunchApp(true);
}
@@ -1664,6 +1662,7 @@
* Called when a gesture from an app has finished, and the animation to the target has ended.
*/
public void onGestureAnimationEnd() {
+ Log.d("b/186444448", "onGestureEnd");
mGestureActive = false;
if (mOrientationState.setGestureActive(false)) {
updateOrientationHandler();
@@ -1677,7 +1676,8 @@
setRunningTaskHidden(false);
animateUpRunningTaskIconScale();
- if (!showAsGrid() || getFocusedTaskView() != null) {
+ if (mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS
+ && (!showAsGrid() || getFocusedTaskView() != null)) {
animateActionsViewIn();
}
@@ -1817,15 +1817,13 @@
}
public void animateUpRunningTaskIconScale() {
- animateUpRunningTaskIconScale(0);
- }
-
- public void animateUpRunningTaskIconScale(float startProgress) {
mRunningTaskIconScaledDown = false;
TaskView firstTask = getRunningTaskView();
+ Log.d("b/186444448", "animateUpRunningTaskIconScale: firstTask="
+ + (firstTask != null ? "t:" + firstTask.getTask() : null));
if (firstTask != null) {
+ firstTask.setIconScaleAnimStartProgress(0f);
firstTask.animateIconScaleAndDimIntoView();
- firstTask.setIconScaleAnimStartProgress(startProgress);
}
}
@@ -1997,29 +1995,12 @@
gridTranslationAnimators.add(taskDismissAnimator);
}
taskView.setGridTranslationX(gridTranslations[i] - snappedTaskGridTranslationX);
- taskView.setNonFullscreenTranslationX(snappedTaskFullscreenScrollAdjustment);
+ taskView.getPrimaryNonFullscreenTranslationProperty().set(taskView,
+ snappedTaskFullscreenScrollAdjustment);
+ taskView.getSecondaryNonFullscreenTranslationProperty().set(taskView, 0f);
}
AnimatorSet gridTranslationAnimatorSet = new AnimatorSet();
gridTranslationAnimatorSet.playTogether(gridTranslationAnimators);
- gridTranslationAnimatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- // Allow the actions view to display again once in focus mode
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- if (getFocusedTaskView() == null) {
- mActionsView.getScrollAlpha().setValue(1);
- }
- }
-
- @Override
- // Hide the actions view if not in focus mode
- public void onAnimationStart(Animator animation) {
- super.onAnimationStart(animation);
- if (getFocusedTaskView() == null) {
- mActionsView.getScrollAlpha().setValue(0);
- }
- }
- });
gridTranslationAnimatorSet.start();
// Use the accumulated translation of the row containing the last task.
@@ -2322,7 +2303,8 @@
snapToPageImmediately(pageToSnapTo);
// Grid got messed up, reapply.
updateGridProperties(taskView, draggedIndex - mTaskViewStartIndex);
- if (showAsGrid() && getFocusedTaskView() == null) {
+ if (showAsGrid() && getFocusedTaskView() == null
+ && mActionsView.getVisibilityAlpha().getValue() == 1) {
animateActionsViewOut();
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 35acdd1..3349b74 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -250,6 +250,58 @@
}
};
+ private static final FloatProperty<TaskView> FULLSCREEN_TRANSLATION_X =
+ new FloatProperty<TaskView>("fullscreenTranslationX") {
+ @Override
+ public void setValue(TaskView taskView, float v) {
+ taskView.setFullscreenTranslationX(v);
+ }
+
+ @Override
+ public Float get(TaskView taskView) {
+ return taskView.mFullscreenTranslationX;
+ }
+ };
+
+ private static final FloatProperty<TaskView> FULLSCREEN_TRANSLATION_Y =
+ new FloatProperty<TaskView>("fullscreenTranslationY") {
+ @Override
+ public void setValue(TaskView taskView, float v) {
+ taskView.setFullscreenTranslationY(v);
+ }
+
+ @Override
+ public Float get(TaskView taskView) {
+ return taskView.mFullscreenTranslationY;
+ }
+ };
+
+ private static final FloatProperty<TaskView> NON_FULLSCREEN_TRANSLATION_X =
+ new FloatProperty<TaskView>("nonFullscreenTranslationX") {
+ @Override
+ public void setValue(TaskView taskView, float v) {
+ taskView.setNonFullscreenTranslationX(v);
+ }
+
+ @Override
+ public Float get(TaskView taskView) {
+ return taskView.mNonFullscreenTranslationX;
+ }
+ };
+
+ private static final FloatProperty<TaskView> NON_FULLSCREEN_TRANSLATION_Y =
+ new FloatProperty<TaskView>("nonFullscreenTranslationY") {
+ @Override
+ public void setValue(TaskView taskView, float v) {
+ taskView.setNonFullscreenTranslationY(v);
+ }
+
+ @Override
+ public Float get(TaskView taskView) {
+ return taskView.mNonFullscreenTranslationY;
+ }
+ };
+
private static final FloatProperty<TaskView> COLOR_TINT =
new FloatProperty<TaskView>("colorTint") {
@Override
@@ -284,9 +336,11 @@
private float mTaskResistanceTranslationY;
// The following translation variables should only be used in the same orientation as Launcher.
private float mFullscreenTranslationX;
+ private float mFullscreenTranslationY;
// Applied as a complement to fullscreenTranslation, for adjusting the carousel overview, or the
// in transition carousel before forming the grid on tablets.
private float mNonFullscreenTranslationX;
+ private float mNonFullscreenTranslationY;
private float mBoxTranslationY;
// The following grid translations scales with mGridProgress.
private float mGridTranslationX;
@@ -740,6 +794,8 @@
}
public void animateIconScaleAndDimIntoView() {
+ Log.d("b/186444448", "animateIconScaleAndDimIntoView: startProgress="
+ + mIconScaleAnimStartProgress);
if (mIconAndDimAnimator != null) {
mIconAndDimAnimator.cancel();
}
@@ -749,6 +805,7 @@
mIconAndDimAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ Log.d("b/186444448", "animateIconScaleAndDimIntoView: end");
mIconAndDimAnimator = null;
}
});
@@ -786,8 +843,9 @@
@Override
public void onRecycle() {
- mFullscreenTranslationX = mNonFullscreenTranslationX =
- mGridTranslationX = mGridTranslationY = mBoxTranslationY = 0f;
+ mFullscreenTranslationX = mFullscreenTranslationY = mNonFullscreenTranslationX =
+ mNonFullscreenTranslationY = mGridTranslationX = mGridTranslationY =
+ mBoxTranslationY = 0f;
resetViewTransforms();
// Clear any references to the thumbnail (it will be re-read either from the cache or the
// system on next bind)
@@ -929,16 +987,26 @@
applyTranslationY();
}
- public void setFullscreenTranslationX(float fullscreenTranslationX) {
+ private void setFullscreenTranslationX(float fullscreenTranslationX) {
mFullscreenTranslationX = fullscreenTranslationX;
applyTranslationX();
}
- public void setNonFullscreenTranslationX(float nonFullscreenTranslationX) {
+ private void setFullscreenTranslationY(float fullscreenTranslationY) {
+ mFullscreenTranslationY = fullscreenTranslationY;
+ applyTranslationY();
+ }
+
+ private void setNonFullscreenTranslationX(float nonFullscreenTranslationX) {
mNonFullscreenTranslationX = nonFullscreenTranslationX;
applyTranslationX();
}
+ private void setNonFullscreenTranslationY(float nonFullscreenTranslationY) {
+ mNonFullscreenTranslationY = nonFullscreenTranslationY;
+ applyTranslationY();
+ }
+
public void setGridTranslationX(float gridTranslationX) {
mGridTranslationX = gridTranslationX;
applyTranslationX();
@@ -960,9 +1028,9 @@
public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
float scrollAdjustment = 0;
if (fullscreenEnabled) {
- scrollAdjustment += mFullscreenTranslationX;
+ scrollAdjustment += getPrimaryFullscreenTranslationProperty().get(this);
} else {
- scrollAdjustment += mNonFullscreenTranslationX;
+ scrollAdjustment += getPrimaryNonFullscreenTranslationProperty().get(this);
}
if (gridEnabled) {
scrollAdjustment += mGridTranslationX;
@@ -1012,7 +1080,10 @@
* change according to a temporary state (e.g. task offset).
*/
public float getPersistentTranslationY() {
- return getGridTrans(mGridTranslationY) + mBoxTranslationY;
+ return mBoxTranslationY
+ + getFullscreenTrans(mFullscreenTranslationY)
+ + getNonFullscreenTrans(mNonFullscreenTranslationY)
+ + getGridTrans(mGridTranslationY);
}
public FloatProperty<TaskView> getPrimaryDismissTranslationProperty() {
@@ -1035,6 +1106,26 @@
TASK_RESISTANCE_TRANSLATION_X, TASK_RESISTANCE_TRANSLATION_Y);
}
+ public FloatProperty<TaskView> getPrimaryFullscreenTranslationProperty() {
+ return getPagedOrientationHandler().getPrimaryValue(
+ FULLSCREEN_TRANSLATION_X, FULLSCREEN_TRANSLATION_Y);
+ }
+
+ public FloatProperty<TaskView> getSecondaryFullscreenTranslationProperty() {
+ return getPagedOrientationHandler().getSecondaryValue(
+ FULLSCREEN_TRANSLATION_X, FULLSCREEN_TRANSLATION_Y);
+ }
+
+ public FloatProperty<TaskView> getPrimaryNonFullscreenTranslationProperty() {
+ return getPagedOrientationHandler().getPrimaryValue(
+ NON_FULLSCREEN_TRANSLATION_X, NON_FULLSCREEN_TRANSLATION_Y);
+ }
+
+ public FloatProperty<TaskView> getSecondaryNonFullscreenTranslationProperty() {
+ return getPagedOrientationHandler().getSecondaryValue(
+ NON_FULLSCREEN_TRANSLATION_X, NON_FULLSCREEN_TRANSLATION_Y);
+ }
+
@Override
public boolean hasOverlappingRendering() {
// TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
diff --git a/res/layout/floating_widget_view.xml b/res/layout/floating_widget_view.xml
new file mode 100644
index 0000000..eea7a92
--- /dev/null
+++ b/res/layout/floating_widget_view.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<com.android.quickstep.views.FloatingWidgetView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layoutDirection="ltr" />
diff --git a/res/layout/widgets_full_sheet_search_and_recommendations.xml b/res/layout/widgets_full_sheet_search_and_recommendations.xml
index e322c6c..bfce01d 100644
--- a/res/layout/widgets_full_sheet_search_and_recommendations.xml
+++ b/res/layout/widgets_full_sheet_search_and_recommendations.xml
@@ -46,7 +46,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/widgets_recommendation_background"
- android:paddingVertical="8dp"
+ android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
android:layout_marginTop="16dp"
android:visibility="gone"/>
</LinearLayout>
diff --git a/res/layout/widgets_list_row_header.xml b/res/layout/widgets_list_row_header.xml
index f4b4130..f20af87 100644
--- a/res/layout/widgets_list_row_header.xml
+++ b/res/layout/widgets_list_row_header.xml
@@ -35,6 +35,7 @@
tools:src="@drawable/ic_corp"/>
<LinearLayout
+ android:id="@+id/app_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 8a160bd..600a550 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -118,6 +118,7 @@
<dimen name="widget_cell_horizontal_padding">16dp</dimen>
<dimen name="widget_cell_font_size">14sp</dimen>
+ <dimen name="recommended_widgets_table_vertical_padding">8dp</dimen>
<dimen name="widget_list_top_bottom_corner_radius">28dp</dimen>
<dimen name="widget_list_content_corner_radius">4dp</dimen>
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index f77c740..620604a 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -72,6 +72,8 @@
// Maintains a list of widget ids which are supposed to be auto advanced.
private static final SparseBooleanArray sAutoAdvanceWidgetIds = new SparseBooleanArray();
+ // Maximum duration for which updates can be deferred.
+ private static final long UPDATE_LOCK_TIMEOUT_MILLIS = 1000;
protected final LayoutInflater mInflater;
@@ -110,6 +112,9 @@
}
}
};
+ private final Object mUpdateLock = new Object();
+ private long mDeferUpdatesUntilMillis = 0;
+ private RemoteViews mMostRecentRemoteViews;
public LauncherAppWidgetHostView(Context context) {
super(context);
@@ -165,6 +170,11 @@
@Override
public void updateAppWidget(RemoteViews remoteViews) {
+ synchronized (mUpdateLock) {
+ mMostRecentRemoteViews = remoteViews;
+ if (SystemClock.uptimeMillis() < mDeferUpdatesUntilMillis) return;
+ }
+
super.updateAppWidget(remoteViews);
// The provider info or the views might have changed.
@@ -198,6 +208,34 @@
return false;
}
+ /**
+ * Begin deferring the application of any {@link RemoteViews} updates made through
+ * {@link #updateAppWidget(RemoteViews)} until {@link #endDeferringUpdates()} has been called or
+ * the next {@link #updateAppWidget(RemoteViews)} call after {@link #UPDATE_LOCK_TIMEOUT_MILLIS}
+ * have elapsed.
+ */
+ public void beginDeferringUpdates() {
+ synchronized (mUpdateLock) {
+ mDeferUpdatesUntilMillis = SystemClock.uptimeMillis() + UPDATE_LOCK_TIMEOUT_MILLIS;
+ }
+ }
+
+ /**
+ * Stop deferring the application of {@link RemoteViews} updates made through
+ * {@link #updateAppWidget(RemoteViews)} and apply the most recently received update.
+ */
+ public void endDeferringUpdates() {
+ RemoteViews remoteViews;
+ synchronized (mUpdateLock) {
+ mDeferUpdatesUntilMillis = 0;
+ remoteViews = mMostRecentRemoteViews;
+ mMostRecentRemoteViews = null;
+ }
+ if (remoteViews != null) {
+ updateAppWidget(remoteViews);
+ }
+ }
+
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
DragLayer dragLayer = mLauncher.getDragLayer();
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index 8794a4a..ccf3187 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -19,7 +19,10 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.util.AttributeSet;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -94,6 +97,32 @@
mTitle = findViewById(R.id.app_title);
mSubtitle = findViewById(R.id.app_subtitle);
mExpandToggle = findViewById(R.id.toggle);
+ findViewById(R.id.app_container).setAccessibilityDelegate(new AccessibilityDelegate() {
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ if (mIsExpanded) {
+ info.removeAction(AccessibilityNodeInfo.ACTION_EXPAND);
+ info.addAction(AccessibilityNodeInfo.ACTION_COLLAPSE);
+ } else {
+ info.removeAction(AccessibilityNodeInfo.ACTION_COLLAPSE);
+ info.addAction(AccessibilityNodeInfo.ACTION_EXPAND);
+ }
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_EXPAND:
+ case AccessibilityNodeInfo.ACTION_COLLAPSE:
+ callOnClick();
+ return true;
+ default:
+ return super.performAccessibilityAction(host, action, args);
+ }
+ }
+ });
}
/**
@@ -106,7 +135,9 @@
// Use the entire touch area of this view to expand / collapse an app widgets section.
setOnClickListener(view -> {
setExpanded(!mIsExpanded);
- onExpandChangeListener.onExpansionChange(mIsExpanded);
+ if (onExpandChangeListener != null) {
+ onExpandChangeListener.onExpansionChange(mIsExpanded);
+ }
});
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 18f1be3..2d3f1a0 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -42,6 +42,7 @@
private static final String TAG = "WidgetsRecommendationTableLayout";
private static final float DOWN_SCALE_RATIO = 0.9f;
private static final float MAX_DOWN_SCALE_RATIO = 0.5f;
+ private final float mWidgetsRecommendationTableVerticalPadding;
private final float mWidgetCellTextViewsHeight;
private final float mWidgetPreviewPadding;
@@ -57,6 +58,8 @@
public WidgetsRecommendationTableLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// There are 1 row for title, 1 row for dimension and 2 rows for description.
+ mWidgetsRecommendationTableVerticalPadding = 2 * getResources()
+ .getDimensionPixelSize(R.dimen.widget_cell_vertical_padding);
mWidgetCellTextViewsHeight = 4 * getResources().getDimension(R.dimen.widget_cell_font_size);
mWidgetPreviewPadding = 2 * getResources()
.getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
@@ -143,7 +146,7 @@
return new RecommendationTableData(List.of(), previewScale);
}
// A naive estimation of the widgets recommendation table height without inflation.
- float totalHeight = 0;
+ float totalHeight = mWidgetsRecommendationTableVerticalPadding;
DeviceProfile deviceProfile = Launcher.getLauncher(getContext()).getDeviceProfile();
for (int i = 0; i < recommendedWidgetsInTable.size(); i++) {
List<WidgetItem> widgetItems = recommendedWidgetsInTable.get(i);