diff options
Diffstat (limited to 'quickstep/src/com/android/quickstep/views')
10 files changed, 1145 insertions, 282 deletions
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java new file mode 100644 index 000000000..d5c43a0f5 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.views; + +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.widget.Button; + +public class ClearAllButton extends Button { + RecentsView mRecentsView; + + public ClearAllButton(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public void setRecentsView(RecentsView recentsView) { + mRecentsView = recentsView; + } + + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + final boolean res = super.performAccessibilityAction(action, arguments); + if (action == ACTION_ACCESSIBILITY_FOCUS) { + mRecentsView.revealClearAllButton(); + } + return res; + } +} diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java new file mode 100644 index 000000000..c359966df --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/IconView.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.views; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.View; + +/** + * A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout + * when the drawable changes. + */ +public class IconView extends View { + + private Drawable mDrawable; + + public IconView(Context context) { + super(context); + } + + public IconView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public IconView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setDrawable(Drawable d) { + if (mDrawable != null) { + mDrawable.setCallback(null); + } + mDrawable = d; + if (mDrawable != null) { + mDrawable.setCallback(this); + mDrawable.setBounds(0, 0, getWidth(), getHeight()); + } + invalidate(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (mDrawable != null) { + mDrawable.setBounds(0, 0, w, h); + } + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return super.verifyDrawable(who) || who == mDrawable; + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + final Drawable drawable = mDrawable; + if (drawable != null && drawable.isStateful() + && drawable.setState(getDrawableState())) { + invalidateDrawable(drawable); + } + } + + @Override + protected void onDraw(Canvas canvas) { + if (mDrawable != null) { + mDrawable.draw(canvas); + } + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } +} diff --git a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java index ac34d90b1..c149de54f 100644 --- a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java +++ b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java @@ -54,7 +54,7 @@ public class LauncherLayoutListener extends AbstractFloatingView @Override public void setInsets(Rect insets) { if (mHandler != null) { - mHandler.onLauncherLayoutChanged(); + mHandler.buildAnimationController(); } } diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java index 4b4af3fa7..950f7fb99 100644 --- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java @@ -27,7 +27,6 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.os.Build; -import android.support.annotation.AnyThread; import android.util.AttributeSet; import android.util.FloatProperty; import android.view.View; @@ -36,6 +35,8 @@ import android.view.ViewDebug; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; +import com.android.quickstep.OverviewInteractionState; +import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.util.LayoutUtils; /** @@ -87,7 +88,11 @@ public class LauncherRecentsView extends RecentsView<Launcher> { public void setTranslationYFactor(float translationFactor) { mTranslationYFactor = translationFactor; - setTranslationY(mTranslationYFactor * (getPaddingBottom() - getPaddingTop())); + setTranslationY(computeTranslationYForFactor(mTranslationYFactor)); + } + + public float computeTranslationYForFactor(float translationYFactor) { + return translationYFactor * (getPaddingBottom() - getPaddingTop()); } @Override @@ -112,8 +117,15 @@ public class LauncherRecentsView extends RecentsView<Launcher> { * Animates adjacent tasks and translate hotseat off screen as well. */ @Override - public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) { - AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv); + public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv, + ClipAnimationHelper helper) { + AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv, helper); + + if (!OverviewInteractionState.getInstance(mActivity).isSwipeUpGestureEnabled()) { + // Hotseat doesn't move when opening recents with the button, + // so don't animate it here either. + return anim; + } float allAppsProgressOffscreen = ALL_APPS_PROGRESS_OFF_SCREEN; LauncherState state = mActivity.getStateManager().getState(); @@ -132,8 +144,19 @@ public class LauncherRecentsView extends RecentsView<Launcher> { LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect); } - @AnyThread - public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) { - LayoutUtils.calculateLauncherTaskSize(context, grid, outRect); + @Override + protected void onTaskLaunched(boolean success) { + if (success) { + mActivity.getStateManager().goToState(NORMAL, false /* animate */); + } else { + LauncherState state = mActivity.getStateManager().getState(); + mActivity.getAllAppsController().setProgress(state.getVerticalProgress(mActivity)); + } + super.onTaskLaunched(success); + } + + @Override + public boolean shouldUseMultiWindowTaskSizeStrategy() { + return mActivity.isInMultiWindowModeCompat(); } } diff --git a/quickstep/src/com/android/quickstep/views/QuickstepDragIndicator.java b/quickstep/src/com/android/quickstep/views/QuickstepDragIndicator.java deleted file mode 100644 index 5e9cd6e4f..000000000 --- a/quickstep/src/com/android/quickstep/views/QuickstepDragIndicator.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep.views; - -import static com.android.launcher3.LauncherState.ALL_APPS; -import static com.android.launcher3.LauncherState.OVERVIEW; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.accessibility.AccessibilityNodeInfo; - -import com.android.launcher3.R; -import com.android.launcher3.userevent.nano.LauncherLogProto.Action; -import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; -import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; -import com.android.launcher3.views.LauncherDragIndicator; - -public class QuickstepDragIndicator extends LauncherDragIndicator { - - public QuickstepDragIndicator(Context context) { - super(context); - } - - public QuickstepDragIndicator(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public QuickstepDragIndicator(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - private boolean isInOverview() { - return mLauncher.isInState(OVERVIEW); - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setContentDescription(getContext().getString(R.string.all_apps_button_label)); - } - - @Override - protected void initCustomActions(AccessibilityNodeInfo info) { - if (!isInOverview()) { - super.initCustomActions(info); - } - } - - @Override - public void onClick(View view) { - mLauncher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, - ControlType.ALL_APPS_BUTTON, - isInOverview() ? ContainerType.TASKSWITCHER : ContainerType.WORKSPACE); - mLauncher.getStateManager().goToState(ALL_APPS); - } -} diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 7e81ba902..a6da89f20 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -16,11 +16,15 @@ package com.android.quickstep.views; +import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; +import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; +import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; @@ -30,13 +34,13 @@ import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; +import android.os.Handler; +import android.os.UserHandle; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; @@ -49,7 +53,6 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug; -import android.view.accessibility.AccessibilityEvent; import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; @@ -64,42 +67,34 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.PendingAnimation; import com.android.launcher3.util.Themes; +import com.android.quickstep.OverviewCallbacks; import com.android.quickstep.QuickScrubController; -import com.android.quickstep.RecentsAnimationInterpolator; -import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds; import com.android.quickstep.RecentsModel; import com.android.quickstep.TaskUtils; +import com.android.quickstep.util.ClipAnimationHelper; +import com.android.quickstep.util.TaskViewDrawable; import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan; import com.android.systemui.shared.recents.model.RecentsTaskLoader; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.TaskStack; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.BackgroundExecutor; +import com.android.systemui.shared.system.PackageManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; import java.util.ArrayList; +import java.util.function.Consumer; /** * A list of recent tasks. */ @TargetApi(Build.VERSION_CODES.P) -public abstract class RecentsView<T extends BaseActivity> - extends PagedView implements OnSharedPreferenceChangeListener, Insettable { +public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable { - private final Rect mTempRect = new Rect(); - - public static final FloatProperty<RecentsView> CONTENT_ALPHA = - new FloatProperty<RecentsView>("contentAlpha") { - @Override - public void setValue(RecentsView recentsView, float v) { - recentsView.setContentAlpha(v); - } + private static final String TAG = RecentsView.class.getSimpleName(); - @Override - public Float get(RecentsView recentsView) { - return recentsView.mContentAlpha; - } - }; + private final Rect mTempRect = new Rect(); public static final FloatProperty<RecentsView> ADJACENT_SCALE = new FloatProperty<RecentsView>("adjacentScale") { @@ -113,8 +108,12 @@ public abstract class RecentsView<T extends BaseActivity> return recentsView.mAdjacentScale; } }; - private static final String PREF_FLIP_RECENTS = "pref_flip_recents"; + public static final boolean FLIP_RECENTS = true; private static final int DISMISS_TASK_DURATION = 300; + // The threshold at which we update the SystemUI flags when animating from the task into the app + private static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.6f; + + private static final float[] sTempFloatArray = new float[3]; protected final T mActivity; private final QuickScrubController mQuickScrubController; @@ -125,19 +124,27 @@ public abstract class RecentsView<T extends BaseActivity> // Keeps track of the previously known visible tasks for purposes of loading/unloading task data private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray(); + private boolean mIsClearAllButtonFullyRevealed; + /** * TODO: Call reloadIdNeeded in onTaskStackChanged. */ private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { @Override public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) { + if (!mHandleTaskStackChanges) { + return; + } updateThumbnail(taskId, snapshot); } @Override public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { + if (!mHandleTaskStackChanges) { + return; + } // Check this is for the right user - if (!checkCurrentUserId(userId, false /* debug */)) { + if (!checkCurrentOrManagedUserId(userId, getContext())) { return; } @@ -150,21 +157,71 @@ public abstract class RecentsView<T extends BaseActivity> @Override public void onActivityUnpinned() { + if (!mHandleTaskStackChanges) { + return; + } // TODO: Re-enable layout transitions for addition of the unpinned task reloadIfNeeded(); } + + @Override + public void onTaskRemoved(int taskId) { + if (!mHandleTaskStackChanges) { + return; + } + BackgroundExecutor.get().submit(() -> { + TaskView taskView = getTaskView(taskId); + if (taskView == null) { + return; + } + Handler handler = taskView.getHandler(); + if (handler == null) { + return; + } + + // TODO: Add callbacks from AM reflecting adding/removing from the recents list, and + // remove all these checks + Task.TaskKey taskKey = taskView.getTask().key; + if (PackageManagerWrapper.getInstance().getActivityInfo(taskKey.getComponent(), + taskKey.userId) == null) { + // The package was uninstalled + handler.post(() -> + dismissTask(taskView, true /* animate */, false /* removeTask */)); + } else { + RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(getContext()); + RecentsTaskLoadPlan.PreloadOptions opts = + new RecentsTaskLoadPlan.PreloadOptions(); + opts.loadTitles = false; + loadPlan.preloadPlan(opts, mModel.getRecentsTaskLoader(), -1, + UserHandle.myUserId()); + if (loadPlan.getTaskStack().findTaskWithId(taskId) == null) { + // The task was removed from the recents list + handler.post(() -> + dismissTask(taskView, true /* animate */, false /* removeTask */)); + } + } + }); + } + + @Override + public void onPinnedStackAnimationStarted() { + // Needed for activities that auto-enter PiP, which will not trigger a remote + // animation to be created + mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER); + } }; private int mLoadPlanId = -1; // Only valid until the launcher state changes to NORMAL private int mRunningTaskId = -1; + private boolean mRunningTaskTileHidden; private Task mTmpRunningTask; - private boolean mFirstTaskIconScaledDown = false; + private boolean mRunningTaskIconScaledDown = false; private boolean mOverviewStateEnabled; - private boolean mTaskStackListenerRegistered; + private boolean mHandleTaskStackChanges; private Runnable mNextPageSwitchRunnable; private boolean mSwipeDownShouldLaunchApp; @@ -178,6 +235,8 @@ public abstract class RecentsView<T extends BaseActivity> // Keeps track of task views whose visual state should not be reset private ArraySet<TaskView> mIgnoreResetTaskViews = new ArraySet<>(); + private View mClearAllButton; + // Variables for empty state private final Drawable mEmptyIcon; private final CharSequence mEmptyMessage; @@ -187,6 +246,14 @@ public abstract class RecentsView<T extends BaseActivity> private boolean mShowEmptyMessage; private Layout mEmptyTextLayout; + private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener = + (inMultiWindowMode) -> { + if (!inMultiWindowMode && mOverviewStateEnabled) { + // TODO: Re-enable layout transitions for addition of the unpinned task + reloadIfNeeded(); + } + }; + public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing)); @@ -199,7 +266,11 @@ public abstract class RecentsView<T extends BaseActivity> mQuickScrubController = new QuickScrubController(mActivity, this); mModel = RecentsModel.getInstance(context); - onSharedPreferenceChanged(Utilities.getPrefs(context), PREF_FLIP_RECENTS); + mIsRtl = Utilities.isRtl(getResources()); + if (FLIP_RECENTS) { + mIsRtl = !mIsRtl; + } + setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents); mEmptyIcon.setCallback(this); @@ -211,17 +282,7 @@ public abstract class RecentsView<T extends BaseActivity> mEmptyMessagePadding = getResources() .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding); setWillNotDraw(false); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { - if (s.equals(PREF_FLIP_RECENTS)) { - mIsRtl = Utilities.isRtl(getResources()); - if (sharedPreferences.getBoolean(PREF_FLIP_RECENTS, false)) { - mIsRtl = !mIsRtl; - } - setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); - } + updateEmptyMessage(); } public boolean isRtl() { @@ -229,14 +290,11 @@ public abstract class RecentsView<T extends BaseActivity> } public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) { - for (int i = 0; i < getChildCount(); i++) { - final TaskView taskView = (TaskView) getChildAt(i); - if (taskView.getTask().key.id == taskId) { - taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData); - return taskView; - } + TaskView taskView = getTaskView(taskId); + if (taskView != null) { + taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData); } - return null; + return taskView; } @Override @@ -249,14 +307,16 @@ public abstract class RecentsView<T extends BaseActivity> protected void onAttachedToWindow() { super.onAttachedToWindow(); updateTaskStackListenerState(); - Utilities.getPrefs(getContext()).registerOnSharedPreferenceChangeListener(this); + mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener); + ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); updateTaskStackListenerState(); - Utilities.getPrefs(getContext()).unregisterOnSharedPreferenceChangeListener(this); + mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener); + ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener); } @Override @@ -271,6 +331,7 @@ public abstract class RecentsView<T extends BaseActivity> loader.unloadTaskData(task); loader.getHighResThumbnailLoader().onTaskInvisible(task); } + onChildViewsChanged(); } public boolean isTaskViewVisible(TaskView tv) { @@ -309,11 +370,69 @@ public abstract class RecentsView<T extends BaseActivity> } } + private int getScrollEnd() { + return mIsRtl ? 0 : mMaxScrollX; + } + + private float calculateClearAllButtonAlpha() { + final int childCount = getChildCount(); + if (mShowEmptyMessage || childCount == 0 || mPageScrolls == null + || childCount != mPageScrolls.length) { + return 0; + } + + final int scrollEnd = getScrollEnd(); + final int oldestChildScroll = getScrollForPage(childCount - 1); + + final int clearAllButtonMotionRange = scrollEnd - oldestChildScroll; + if (clearAllButtonMotionRange == 0) return 0; + + final float alphaUnbound = ((float) (getScrollX() - oldestChildScroll)) / + clearAllButtonMotionRange; + if (alphaUnbound > 1) return 0; + + return Math.max(alphaUnbound, 0); + } + + private void updateClearAllButtonAlpha() { + if (mClearAllButton != null) { + final float alpha = calculateClearAllButtonAlpha(); + mIsClearAllButtonFullyRevealed = alpha == 1; + mClearAllButton.setAlpha(alpha * mContentAlpha); + } + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + updateClearAllButtonAlpha(); + } + + @Override + protected void restoreScrollOnLayout() { + if (mIsClearAllButtonFullyRevealed) { + scrollAndForceFinish(getScrollEnd()); + } else { + super.restoreScrollOnLayout(); + } + } + @Override public boolean onTouchEvent(MotionEvent ev) { - super.onTouchEvent(ev); - // Do not let touch escape to siblings below this view. - return true; + if (ev.getAction() == MotionEvent.ACTION_DOWN && mTouchState == TOUCH_STATE_REST + && mScroller.isFinished() && mIsClearAllButtonFullyRevealed) { + mClearAllButton.getHitRect(mTempRect); + mTempRect.offset(-getLeft(), -getTop()); + if (mTempRect.contains((int) ev.getX(), (int) ev.getY())) { + // If nothing is in motion, let the Clear All button process the event. + return false; + } + } + + if (ev.getAction() == MotionEvent.ACTION_UP && mShowEmptyMessage) { + onAllTasksRemoved(); + } + return super.onTouchEvent(ev); } private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) { @@ -356,7 +475,6 @@ public abstract class RecentsView<T extends BaseActivity> taskView.bind(task); } resetTaskVisuals(); - applyIconScale(false /* animate */); if (oldChildCount != getChildCount()) { mQuickScrubController.snapToNextTaskIfAvailable(); @@ -373,6 +491,10 @@ public abstract class RecentsView<T extends BaseActivity> taskView.resetVisualProperties(); } } + if (mRunningTaskTileHidden) { + setRunningTaskHidden(mRunningTaskTileHidden); + } + applyIconScale(false /* animate */); updateCurveProperties(); // Update the set of visible task's data @@ -380,18 +502,13 @@ public abstract class RecentsView<T extends BaseActivity> } private void updateTaskStackListenerState() { - boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow() + boolean handleTaskStackChanges = mOverviewStateEnabled && isAttachedToWindow() && getWindowVisibility() == VISIBLE; - if (registerStackListener != mTaskStackListenerRegistered) { - if (registerStackListener) { - ActivityManagerWrapper.getInstance() - .registerTaskStackListener(mTaskStackListener); + if (handleTaskStackChanges != mHandleTaskStackChanges) { + mHandleTaskStackChanges = handleTaskStackChanges; + if (handleTaskStackChanges) { reloadIfNeeded(); - } else { - ActivityManagerWrapper.getInstance() - .unregisterTaskStackListener(mTaskStackListener); } - mTaskStackListenerRegistered = registerStackListener; } } @@ -400,15 +517,20 @@ public abstract class RecentsView<T extends BaseActivity> mInsets.set(insets); DeviceProfile dp = mActivity.getDeviceProfile(); getTaskSize(dp, mTempRect); + mTempRect.top -= getResources() .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin); setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top, - dp.widthPx - mTempRect.right - mInsets.right, - dp.heightPx - mTempRect.bottom - mInsets.bottom); + dp.availableWidthPx + mInsets.left - mTempRect.right, + dp.availableHeightPx + mInsets.top - mTempRect.bottom); } protected abstract void getTaskSize(DeviceProfile dp, Rect outRect); + public void getTaskSize(Rect outRect) { + getTaskSize(mActivity.getDeviceProfile(), outRect); + } + @Override protected boolean computeScrollHelper() { boolean scrolling = super.computeScrollHelper(); @@ -506,13 +628,16 @@ public abstract class RecentsView<T extends BaseActivity> mHasVisibleTaskData.clear(); } - protected abstract void onAllTasksRemoved(); public void reset() { - unloadVisibleTaskData(); mRunningTaskId = -1; + mRunningTaskTileHidden = false; + + unloadVisibleTaskData(); setCurrentPage(0); + + OverviewCallbacks.get(getContext()).onResetOverview(); } /** @@ -546,12 +671,17 @@ public abstract class RecentsView<T extends BaseActivity> new ActivityManager.TaskDescription(), 0, new ComponentName("", ""), false); taskView.bind(mTmpRunningTask); } - setCurrentTask(mRunningTaskId); + setCurrentTask(runningTaskId); + } - // Hide the task that we are animating into, ignore if there is no associated task (ie. the - // assistant) - if (getPageAt(mCurrentPage) != null) { - getPageAt(mCurrentPage).setAlpha(0); + /** + * Hides the tile associated with {@link #mRunningTaskId} + */ + public void setRunningTaskHidden(boolean isHidden) { + mRunningTaskTileHidden = isHidden; + TaskView runningTask = getTaskView(mRunningTaskId); + if (runningTask != null) { + runningTask.setAlpha(isHidden ? 0 : mContentAlpha); } } @@ -559,7 +689,15 @@ public abstract class RecentsView<T extends BaseActivity> * Similar to {@link #showTask(int)} but does not put any restrictions on the first tile. */ public void setCurrentTask(int runningTaskId) { + boolean runningTaskTileHidden = mRunningTaskTileHidden; + boolean runningTaskIconScaledDown = mRunningTaskIconScaledDown; + + setRunningTaskIconScaledDown(false, false); + setRunningTaskHidden(false); mRunningTaskId = runningTaskId; + setRunningTaskIconScaledDown(runningTaskIconScaledDown, false); + setRunningTaskHidden(runningTaskTileHidden); + setCurrentPage(0); // Load the tasks (if the loading is already @@ -587,22 +725,22 @@ public abstract class RecentsView<T extends BaseActivity> return mQuickScrubController; } - public void setFirstTaskIconScaledDown(boolean isScaledDown, boolean animate) { - if (mFirstTaskIconScaledDown == isScaledDown) { + public void setRunningTaskIconScaledDown(boolean isScaledDown, boolean animate) { + if (mRunningTaskIconScaledDown == isScaledDown) { return; } - mFirstTaskIconScaledDown = isScaledDown; + mRunningTaskIconScaledDown = isScaledDown; applyIconScale(animate); } private void applyIconScale(boolean animate) { - float scale = mFirstTaskIconScaledDown ? 0 : 1; - TaskView firstTask = (TaskView) getChildAt(0); + float scale = mRunningTaskIconScaledDown ? 0 : 1; + TaskView firstTask = getTaskView(mRunningTaskId); if (firstTask != null) { if (animate) { - firstTask.animateIconToScale(scale); + firstTask.animateIconToScaleAndDim(scale); } else { - firstTask.setIconScale(scale); + firstTask.setIconScaleAndDim(scale); } } } @@ -640,8 +778,26 @@ public abstract class RecentsView<T extends BaseActivity> mIgnoreResetTaskViews.remove(taskView); } + private void addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration) { + addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim); + addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()), + duration, LINEAR, anim); + } + + private void removeTask(Task task, int index, PendingAnimation.OnEndListener onEndListener, + boolean shouldLog) { + if (task != null) { + ActivityManagerWrapper.getInstance().removeTask(task.key.id); + if (shouldLog) { + mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss( + onEndListener.logAction, Direction.UP, index, + TaskUtils.getComponentKeyForTask(task.key)); + } + } + } + public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView, - boolean removeTask, long duration) { + boolean shouldRemoveTask, long duration) { if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) { throw new IllegalStateException("Another pending animation is still running"); } @@ -659,26 +815,43 @@ public abstract class RecentsView<T extends BaseActivity> int[] newScroll = new int[count]; getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView); - int maxScrollDiff = 0; - int lastPage = mIsRtl ? 0 : count - 1; - if (getChildAt(lastPage) == taskView) { - if (count > 1) { - int secondLastPage = mIsRtl ? 1 : count - 2; - maxScrollDiff = oldScroll[lastPage] - newScroll[secondLastPage]; - } + int scrollDiffPerPage = 0; + int leftmostPage = mIsRtl ? count -1 : 0; + int rightmostPage = mIsRtl ? 0 : count - 1; + if (count > 1) { + int secondRightmostPage = mIsRtl ? 1 : count - 2; + scrollDiffPerPage = oldScroll[rightmostPage] - oldScroll[secondRightmostPage]; } + int draggedIndex = indexOfChild(taskView); boolean needsCurveUpdates = false; for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child == taskView) { if (animateTaskView) { - addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim); - addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()), - duration, LINEAR, anim); + addDismissedTaskAnimations(taskView, anim, duration); } } else { - int scrollDiff = newScroll[i] - oldScroll[i] + maxScrollDiff; + // If we just take newScroll - oldScroll, everything to the right of dragged task + // translates to the left. We need to offset this in some cases: + // - In RTL, add page offset to all pages, since we want pages to move to the right + // Additionally, add a page offset if: + // - Current page is rightmost page (leftmost for RTL) + // - Dragging an adjacent page on the left side (right side for RTL) + int offset = mIsRtl ? scrollDiffPerPage : 0; + if (mCurrentPage == draggedIndex) { + int lastPage = mIsRtl ? leftmostPage : rightmostPage; + if (mCurrentPage == lastPage) { + offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; + } + } else { + // Dragging an adjacent page. + int negativeAdjacent = mCurrentPage - 1; // (Right in RTL, left in LTR) + if (draggedIndex == negativeAdjacent) { + offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; + } + } + int scrollDiff = newScroll[i] - oldScroll[i] + offset; if (scrollDiff != 0) { addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff), duration, ACCEL, anim); @@ -701,18 +874,18 @@ public abstract class RecentsView<T extends BaseActivity> mPendingAnimation = pendingAnimation; mPendingAnimation.addEndListener((onEndListener) -> { if (onEndListener.isSuccess) { - if (removeTask) { - Task task = taskView.getTask(); - if (task != null) { - ActivityManagerWrapper.getInstance().removeTask(task.key.id); - mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss( - onEndListener.logAction, Direction.UP, - TaskUtils.getComponentKeyForTask(task.key)); - } + if (shouldRemoveTask) { + removeTask(taskView.getTask(), draggedIndex, onEndListener, true); + } + int pageToSnapTo = mCurrentPage; + if (draggedIndex < pageToSnapTo) { + pageToSnapTo -= 1; } removeView(taskView); if (getChildCount() == 0) { onAllTasksRemoved(); + } else if (!mIsClearAllButtonFullyRevealed) { + snapToPageImmediately(pageToSnapTo); } } resetTaskVisuals(); @@ -721,6 +894,33 @@ public abstract class RecentsView<T extends BaseActivity> return pendingAnimation; } + public PendingAnimation createAllTasksDismissAnimation(long duration) { + if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) { + throw new IllegalStateException("Another pending animation is still running"); + } + AnimatorSet anim = new AnimatorSet(); + PendingAnimation pendingAnimation = new PendingAnimation(anim); + + int count = getChildCount(); + for (int i = 0; i < count; i++) { + addDismissedTaskAnimations(getChildAt(i), anim, duration); + } + + mPendingAnimation = pendingAnimation; + mPendingAnimation.addEndListener((onEndListener) -> { + if (onEndListener.isSuccess) { + while (getChildCount() != 0) { + TaskView taskView = getPageAt(getChildCount() - 1); + removeTask(taskView.getTask(), -1, onEndListener, false); + removeView(taskView); + } + onAllTasksRemoved(); + } + mPendingAnimation = null; + }); + return pendingAnimation; + } + private static void addAnim(ObjectAnimator anim, long duration, TimeInterpolator interpolator, AnimatorSet set) { anim.setDuration(duration).setInterpolator(interpolator); @@ -744,9 +944,7 @@ public abstract class RecentsView<T extends BaseActivity> } } - public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) { - PendingAnimation pendingAnim = createTaskDismissAnimation(taskView, animateTaskView, - removeTask, DISMISS_TASK_DURATION); + private void runDismissAnimation(PendingAnimation pendingAnim) { AnimatorPlaybackController controller = AnimatorPlaybackController.wrap( pendingAnim.anim, DISMISS_TASK_DURATION); controller.dispatchOnStart(); @@ -755,6 +953,15 @@ public abstract class RecentsView<T extends BaseActivity> controller.start(); } + public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) { + runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask, + DISMISS_TASK_DURATION)); + } + + public void dismissAllTasks() { + runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION)); + } + @Override public boolean dispatchKeyEvent(KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN) { @@ -789,21 +996,24 @@ public abstract class RecentsView<T extends BaseActivity> snapToPageRelative(1); } - public void setContentAlpha(float alpha) { - if (mContentAlpha == alpha) { - return; - } + public float getContentAlpha() { + return mContentAlpha; + } + public void setContentAlpha(float alpha) { + alpha = Utilities.boundToRange(alpha, 0, 1); mContentAlpha = alpha; for (int i = getChildCount() - 1; i >= 0; i--) { - getChildAt(i).setAlpha(alpha); + TaskView child = getPageAt(i); + if (!mRunningTaskTileHidden || child.getTask().key.id != mRunningTaskId) { + getChildAt(i).setAlpha(alpha); + } } int alphaInt = Math.round(alpha * 255); mEmptyMessagePaint.setAlpha(alphaInt); mEmptyIcon.setAlpha(alphaInt); - - setVisibility(alpha > 0 ? VISIBLE : GONE); + updateClearAllButtonAlpha(); } public void setAdjacentScale(float adjacentScale) { @@ -815,15 +1025,13 @@ public abstract class RecentsView<T extends BaseActivity> if (currTask == null) { return; } - currTask.setScaleX(mAdjacentScale); - currTask.setScaleY(mAdjacentScale); + currTask.setZoomScale(mAdjacentScale); if (mCurrentPage - 1 >= 0) { TaskView adjacentTask = getPageAt(mCurrentPage - 1); float[] scaleAndTranslation = getAdjacentScaleAndTranslation(currTask, adjacentTask, mAdjacentScale, 0); - adjacentTask.setScaleX(scaleAndTranslation[0]); - adjacentTask.setScaleY(scaleAndTranslation[0]); + adjacentTask.setZoomScale(scaleAndTranslation[0]); adjacentTask.setTranslationX(-scaleAndTranslation[1]); adjacentTask.setTranslationY(scaleAndTranslation[2]); } @@ -831,8 +1039,7 @@ public abstract class RecentsView<T extends BaseActivity> TaskView adjacentTask = getPageAt(mCurrentPage + 1); float[] scaleAndTranslation = getAdjacentScaleAndTranslation(currTask, adjacentTask, mAdjacentScale, 0); - adjacentTask.setScaleX(scaleAndTranslation[0]); - adjacentTask.setScaleY(scaleAndTranslation[0]); + adjacentTask.setZoomScale(scaleAndTranslation[0]); adjacentTask.setTranslationX(scaleAndTranslation[1]); adjacentTask.setTranslationY(scaleAndTranslation[2]); } @@ -841,11 +1048,10 @@ public abstract class RecentsView<T extends BaseActivity> private float[] getAdjacentScaleAndTranslation(TaskView currTask, TaskView adjacentTask, float currTaskToScale, float currTaskToTranslationY) { float displacement = currTask.getWidth() * (currTaskToScale - currTask.getCurveScale()); - return new float[] { - currTaskToScale * adjacentTask.getCurveScale(), - mIsRtl ? -displacement : displacement, - currTaskToTranslationY - }; + sTempFloatArray[0] = currTaskToScale; + sTempFloatArray[1] = mIsRtl ? -displacement : displacement; + sTempFloatArray[2] = currTaskToTranslationY; + return sTempFloatArray; } @Override @@ -853,6 +1059,7 @@ public abstract class RecentsView<T extends BaseActivity> super.onViewAdded(child); child.setAlpha(mContentAlpha); setAdjacentScale(mAdjacentScale); + onChildViewsChanged(); } @Override @@ -883,10 +1090,11 @@ public abstract class RecentsView<T extends BaseActivity> boolean hasValidSize = getWidth() > 0 && getHeight() > 0; if (sizeChanged && hasValidSize) { mEmptyTextLayout = null; + mLastMeasureSize.set(getWidth(), getHeight()); } + updateClearAllButtonAlpha(); if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) { - mLastMeasureSize.set(getWidth(), getHeight()); int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding; mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(), mEmptyMessagePaint, availableWidth) @@ -909,8 +1117,13 @@ public abstract class RecentsView<T extends BaseActivity> protected void maybeDrawEmptyMessage(Canvas canvas) { if (mShowEmptyMessage && mEmptyTextLayout != null) { - mEmptyIcon.draw(canvas); + // Offset to center in the visible (non-padded) part of RecentsView + mTempRect.set(mInsets.left + getPaddingLeft(), mInsets.top + getPaddingTop(), + mInsets.right + getPaddingRight(), mInsets.bottom + getPaddingBottom()); canvas.save(); + canvas.translate(getScrollX() + (mTempRect.left - mTempRect.right) / 2, + (mTempRect.top - mTempRect.bottom) / 2); + mEmptyIcon.draw(canvas); canvas.translate(mEmptyMessagePadding, mEmptyIcon.getBounds().bottom + mEmptyMessagePadding); mEmptyTextLayout.draw(canvas); @@ -924,17 +1137,18 @@ public abstract class RecentsView<T extends BaseActivity> * If launching one of the adjacent tasks, parallax the center task and other adjacent task * to the right. */ - public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) { + public AnimatorSet createAdjacentPageAnimForTaskLaunch( + TaskView tv, ClipAnimationHelper clipAnimationHelper) { AnimatorSet anim = new AnimatorSet(); int taskIndex = indexOfChild(tv); int centerTaskIndex = getCurrentPage(); boolean launchingCenterTask = taskIndex == centerTaskIndex; - TaskWindowBounds endInterpolation = tv.getRecentsInterpolator().interpolate(1); - float toScale = endInterpolation.taskScale; - float toTranslationY = endInterpolation.taskY; - + float toScale = clipAnimationHelper.getSourceRect().width() + / clipAnimationHelper.getTargetRect().width(); + float toTranslationY = clipAnimationHelper.getSourceRect().centerY() + - clipAnimationHelper.getTargetRect().centerY(); if (launchingCenterTask) { TaskView centerTask = getPageAt(centerTaskIndex); if (taskIndex - 1 >= 0) { @@ -968,68 +1182,143 @@ public abstract class RecentsView<T extends BaseActivity> return anim; } - private ObjectAnimator createAnimForChild(View child, float[] toScaleAndTranslation) { - return ObjectAnimator.ofPropertyValuesHolder(child, + private Animator createAnimForChild(TaskView child, float[] toScaleAndTranslation) { + AnimatorSet anim = new AnimatorSet(); + anim.play(ObjectAnimator.ofFloat(child, TaskView.ZOOM_SCALE, toScaleAndTranslation[0])); + anim.play(ObjectAnimator.ofPropertyValuesHolder(child, new PropertyListBuilder() - .scale(child.getScaleX() * toScaleAndTranslation[0]) .translationX(toScaleAndTranslation[1]) .translationY(toScaleAndTranslation[2]) - .build()); + .build())); + return anim; } public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) { if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) { throw new IllegalStateException("Another pending animation is still running"); } - AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv); int count = getChildCount(); if (count == 0) { - return new PendingAnimation(anim); - } - - final RecentsAnimationInterpolator recentsInterpolator = tv.getRecentsInterpolator(); - ValueAnimator targetViewAnim = ValueAnimator.ofFloat(0, 1); - targetViewAnim.addUpdateListener((animation) -> { - float percent = animation.getAnimatedFraction(); - TaskWindowBounds tw = recentsInterpolator.interpolate(percent); - tv.setScaleX(tw.taskScale); - tv.setScaleY(tw.taskScale); - tv.setTranslationX(tw.taskX); - tv.setTranslationY(tw.taskY); + return new PendingAnimation(new AnimatorSet()); + } + + tv.setVisibility(INVISIBLE); + int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags(); + TaskViewDrawable drawable = new TaskViewDrawable(tv, this); + getOverlay().add(drawable); + + ObjectAnimator drawableAnim = + ObjectAnimator.ofFloat(drawable, TaskViewDrawable.PROGRESS, 1, 0); + drawableAnim.setInterpolator(LINEAR); + drawableAnim.addUpdateListener((animator) -> { + // Once we pass a certain threshold, update the sysui flags to match the target tasks' + // flags + mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, + animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD + ? targetSysUiFlags + : 0); }); - anim.play(targetViewAnim); + + AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, + drawable.getClipAnimationHelper()); + anim.play(drawableAnim); anim.setDuration(duration); + Consumer<Boolean> onTaskLaunchFinish = (result) -> { + onTaskLaunched(result); + tv.setVisibility(VISIBLE); + getOverlay().remove(drawable); + }; + mPendingAnimation = new PendingAnimation(anim); mPendingAnimation.addEndListener((onEndListener) -> { if (onEndListener.isSuccess) { - tv.launchTask(false); + Consumer<Boolean> onLaunchResult = (result) -> { + onTaskLaunchFinish.accept(result); + if (!result) { + tv.notifyTaskLaunchFailed(TAG); + } + }; + tv.launchTask(false, onLaunchResult, getHandler()); Task task = tv.getTask(); if (task != null) { mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss( - onEndListener.logAction, Direction.DOWN, + onEndListener.logAction, Direction.DOWN, indexOfChild(tv), TaskUtils.getComponentKeyForTask(task.key)); } } else { - resetTaskVisuals(); + onTaskLaunchFinish.accept(false); } mPendingAnimation = null; }); return mPendingAnimation; } + public abstract boolean shouldUseMultiWindowTaskSizeStrategy(); + + protected void onTaskLaunched(boolean success) { + resetTaskVisuals(); + } + @Override protected void notifyPageSwitchListener(int prevPage) { super.notifyPageSwitchListener(prevPage); - View currChild = getChildAt(mCurrentPage); - if (currChild != null) { - currChild.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); - } + loadVisibleTaskData(); } @Override protected String getCurrentPageDescription() { return ""; } + + private int additionalScrollForClearAllButton() { + return (int) getResources().getDimension( + R.dimen.clear_all_container_width) - getPaddingEnd(); + } + + @Override + protected int computeMaxScrollX() { + if (getChildCount() == 0) { + return super.computeMaxScrollX(); + } + + // Allow a clear_all_container_width-sized gap after the last task. + return super.computeMaxScrollX() + (mIsRtl ? 0 : additionalScrollForClearAllButton()); + } + + @Override + protected int offsetForPageScrolls() { + return mIsRtl ? additionalScrollForClearAllButton() : 0; + } + + public void setClearAllButton(View clearAllButton) { + mClearAllButton = clearAllButton; + updateClearAllButtonAlpha(); + } + + private void onChildViewsChanged() { + final int childCount = getChildCount(); + mClearAllButton.setVisibility(childCount == 0 ? INVISIBLE : VISIBLE); + } + + public void revealClearAllButton() { + scrollTo(mIsRtl ? 0 : computeMaxScrollX(), 0); + } + + @Override + public void addChildrenForAccessibility(ArrayList<View> outChildren) { + if (FLIP_RECENTS) { + for (int i = getChildCount() - 1; i >= 0; --i) { + outChildren.add(getChildAt(i)); + } + } else { + super.addChildrenForAccessibility(outChildren); + } + } + + @Override + protected boolean isPageOrderFlipped() { + return FLIP_RECENTS; + } } diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java new file mode 100644 index 000000000..429432b2f --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.views; + +import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; +import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.FloatProperty; +import android.view.Gravity; +import android.view.MotionEvent; + +import com.android.launcher3.InsettableFrameLayout; +import com.android.launcher3.R; + +public class RecentsViewContainer extends InsettableFrameLayout { + public static final FloatProperty<RecentsViewContainer> CONTENT_ALPHA = + new FloatProperty<RecentsViewContainer>("contentAlpha") { + @Override + public void setValue(RecentsViewContainer view, float v) { + view.setContentAlpha(v); + } + + @Override + public Float get(RecentsViewContainer view) { + return view.mRecentsView.getContentAlpha(); + } + }; + + private final Rect mTempRect = new Rect(); + + private RecentsView mRecentsView; + private ClearAllButton mClearAllButton; + + public RecentsViewContainer(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mClearAllButton = findViewById(R.id.clear_all_button); + mClearAllButton.setOnClickListener((v) -> { + mRecentsView.mActivity.getUserEventDispatcher() + .logActionOnControl(TAP, CLEAR_ALL_BUTTON); + mRecentsView.dismissAllTasks(); + }); + + mRecentsView = findViewById(R.id.overview_panel); + final InsettableFrameLayout.LayoutParams params = + (InsettableFrameLayout.LayoutParams) mClearAllButton.getLayoutParams(); + params.gravity = Gravity.TOP | (RecentsView.FLIP_RECENTS ? Gravity.START : Gravity.END); + mClearAllButton.setLayoutParams(params); + mClearAllButton.forceHasOverlappingRendering(false); + + mRecentsView.setClearAllButton(mClearAllButton); + mClearAllButton.setRecentsView(mRecentsView); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + mRecentsView.getTaskSize(mTempRect); + + mClearAllButton.setTranslationX( + (mRecentsView.isRtl() ? 1 : -1) * + (getResources().getDimension(R.dimen.clear_all_container_width) + - mClearAllButton.getMeasuredWidth()) / 2); + mClearAllButton.setTranslationY( + mTempRect.top + (mTempRect.height() - mClearAllButton.getMeasuredHeight()) / 2 + - mClearAllButton.getTop()); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + super.onTouchEvent(ev); + // Do not let touch escape to siblings below this view. This prevents scrolling of the + // workspace while in Recents. + return true; + } + + public void setContentAlpha(float alpha) { + if (alpha == mRecentsView.getContentAlpha()) { + return; + } + mRecentsView.setContentAlpha(alpha); + setVisibility(alpha > 0 ? VISIBLE : GONE); + } +}
\ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java new file mode 100644 index 000000000..24afd4868 --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.views; + +import static android.support.v4.graphics.ColorUtils.compositeColors; +import static android.support.v4.graphics.ColorUtils.setAlphaComponent; + +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.anim.Interpolators.ACCEL_2; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Path.Direction; +import android.graphics.Path.Op; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.util.AttributeSet; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.R; +import com.android.launcher3.uioverrides.OverviewState; +import com.android.launcher3.util.Themes; +import com.android.launcher3.views.ScrimView; + +/** + * Scrim used for all-apps and shelf in Overview + * In transposed layout, it behaves as a simple color scrim. + * In portrait layout, it draws a rounded rect such that + * From normal state to overview state, the shelf just fades in and does not move + * From overview state to all-apps state the shelf moves up and fades in to cover the screen + */ +public class ShelfScrimView extends ScrimView { + + private static final int THRESHOLD_ALPHA_DARK = 102; + private static final int THRESHOLD_ALPHA_LIGHT = 46; + private static final int THRESHOLD_ALPHA_SUPER_LIGHT = 128; + private static final int CLEAR_ALL_TASKS = R.string.recents_clear_all; + + // In transposed layout, we simply draw a flat color. + private boolean mDrawingFlatColor; + + // For shelf mode + private final int mEndAlpha; + private final int mThresholdAlpha; + private final float mRadius; + private final float mMaxScrimAlpha; + private final Paint mPaint; + + // Max vertical progress after which the scrim stops moving. + private float mMoveThreshold; + // Minimum visible size of the scrim. + private int mMinSize; + + private float mScrimMoveFactor = 0; + private int mShelfColor; + private int mRemainingScreenColor; + + private final Path mTempPath = new Path(); + private final Path mRemainingScreenPath = new Path(); + private boolean mRemainingScreenPathValid = false; + + public ShelfScrimView(Context context, AttributeSet attrs) { + super(context, attrs); + mMaxScrimAlpha = OVERVIEW.getWorkspaceScrimAlpha(mLauncher); + + mEndAlpha = Color.alpha(mEndScrim); + if (Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark)) { + mThresholdAlpha = THRESHOLD_ALPHA_DARK; + } else if (Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) { + mThresholdAlpha = THRESHOLD_ALPHA_SUPER_LIGHT; + } else { + mThresholdAlpha = THRESHOLD_ALPHA_LIGHT; + } + mRadius = mLauncher.getResources().getDimension(R.dimen.shelf_surface_radius); + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + // Just assume the easiest UI for now, until we have the proper layout information. + mDrawingFlatColor = true; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mRemainingScreenPathValid = false; + } + + @Override + public void reInitUi() { + DeviceProfile dp = mLauncher.getDeviceProfile(); + mDrawingFlatColor = dp.isVerticalBarLayout(); + + if (!mDrawingFlatColor) { + float swipeLength = OverviewState.getDefaultSwipeHeight(mLauncher); + mMoveThreshold = 1 - swipeLength / mLauncher.getAllAppsController().getShiftRange(); + mMinSize = dp.hotseatBarSizePx + dp.getInsets().bottom; + mRemainingScreenPathValid = false; + updateColors(); + } + updateDragHandleAlpha(); + invalidate(); + } + + @Override + public void updateColors() { + super.updateColors(); + if (mDrawingFlatColor) { + return; + } + + if (mProgress >= mMoveThreshold) { + mScrimMoveFactor = 1; + + if (mProgress >= 1) { + mShelfColor = 0; + } else { + int alpha = Math.round(mThresholdAlpha * ACCEL_2.getInterpolation( + (1 - mProgress) / (1 - mMoveThreshold))); + mShelfColor = setAlphaComponent(mEndScrim, alpha); + } + + mRemainingScreenColor = 0; + } else if (mProgress <= 0) { + mScrimMoveFactor = 0; + mShelfColor = mCurrentFlatColor; + mRemainingScreenColor = 0; + + } else { + mScrimMoveFactor = mProgress / mMoveThreshold; + mRemainingScreenColor = setAlphaComponent(mScrimColor, + Math.round((1 - mScrimMoveFactor) * mMaxScrimAlpha * 255)); + + // Merge the remainingScreenColor and shelfColor in one to avoid overdraw. + int alpha = mEndAlpha - Math.round((mEndAlpha - mThresholdAlpha) * mScrimMoveFactor); + mShelfColor = compositeColors(setAlphaComponent(mEndScrim, alpha), + mRemainingScreenColor); + } + } + + @Override + protected void updateDragHandleAlpha() { + if (mDrawingFlatColor) { + super.updateDragHandleAlpha(); + } else if (mDragHandle != null) { + mDragHandle.setAlpha(255); + } + } + + @Override + protected void onDraw(Canvas canvas) { + float translate = drawBackground(canvas); + + if (mDragHandle != null) { + canvas.translate(0, -translate); + mDragHandle.draw(canvas); + canvas.translate(0, translate); + } + } + + private float drawBackground(Canvas canvas) { + if (mDrawingFlatColor) { + if (mCurrentFlatColor != 0) { + canvas.drawColor(mCurrentFlatColor); + } + return 0; + } + + if (mShelfColor == 0) { + return 0; + } else if (mScrimMoveFactor <= 0) { + canvas.drawColor(mShelfColor); + return getHeight(); + } + + float minTop = getHeight() - mMinSize; + float top = minTop * mScrimMoveFactor - mDragHandleSize; + + // Draw the scrim over the remaining screen if needed. + if (mRemainingScreenColor != 0) { + if (!mRemainingScreenPathValid) { + mTempPath.reset(); + // Using a arbitrary '+10' in the bottom to avoid any left-overs at the + // corners due to rounding issues. + mTempPath.addRoundRect(0, minTop, getWidth(), getHeight() + mRadius + 10, + mRadius, mRadius, Direction.CW); + + mRemainingScreenPath.reset(); + mRemainingScreenPath.addRect(0, 0, getWidth(), getHeight(), Direction.CW); + mRemainingScreenPath.op(mTempPath, Op.DIFFERENCE); + } + + float offset = minTop - top; + canvas.translate(0, -offset); + mPaint.setColor(mRemainingScreenColor); + canvas.drawPath(mRemainingScreenPath, mPaint); + canvas.translate(0, offset); + } + + mPaint.setColor(mShelfColor); + canvas.drawRoundRect(0, top, getWidth(), getHeight() + mRadius, + mRadius, mRadius, mPaint); + return minTop - mDragHandleSize - top; + } + + @NonNull + @Override + protected AccessibilityHelper createAccessibilityHelper() { + return new ShelfScrimAccessibilityHelper(); + } + + protected class ShelfScrimAccessibilityHelper extends AccessibilityHelper { + @Override + protected void onPopulateNodeForVirtualView(int virtualViewId, + AccessibilityNodeInfoCompat node) { + super.onPopulateNodeForVirtualView(virtualViewId, node); + + if (mLauncher.isInState(OVERVIEW)) { + final RecentsView overviewPanel = mLauncher.getOverviewPanel(); + if (overviewPanel.getChildCount() != 0) { + node.addAction( + new AccessibilityNodeInfoCompat.AccessibilityActionCompat( + CLEAR_ALL_TASKS, + getContext().getText(CLEAR_ALL_TASKS))); + } + } + } + + @Override + protected boolean onPerformActionForVirtualView( + int virtualViewId, int action, Bundle arguments) { + if (super.onPerformActionForVirtualView(virtualViewId, action, arguments)) return true; + + if (action == CLEAR_ALL_TASKS) { + if (mLauncher.isInState(OVERVIEW)) { + mLauncher.<RecentsView>getOverviewPanel().dismissAllTasks(); + } + return true; + } + + return false; + } + } +} diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java index 58b7db7b2..128a19e06 100644 --- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java @@ -16,6 +16,8 @@ package com.android.quickstep.views; +import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN; + import android.content.Context; import android.content.res.Configuration; import android.graphics.Bitmap; @@ -28,12 +30,15 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Shader; import android.util.AttributeSet; +import android.util.FloatProperty; +import android.util.Property; import android.view.View; import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.SystemUiController; import com.android.quickstep.TaskOverlayFactory; import com.android.quickstep.TaskOverlayFactory.TaskOverlay; import com.android.systemui.shared.recents.model.Task; @@ -46,6 +51,19 @@ public class TaskThumbnailView extends View { private static final LightingColorFilter[] sDimFilterCache = new LightingColorFilter[256]; + public static final Property<TaskThumbnailView, Float> DIM_ALPHA_MULTIPLIER = + new FloatProperty<TaskThumbnailView>("dimAlphaMultiplier") { + @Override + public void setValue(TaskThumbnailView thumbnail, float dimAlphaMultiplier) { + thumbnail.setDimAlphaMultipler(dimAlphaMultiplier); + } + + @Override + public Float get(TaskThumbnailView thumbnailView) { + return thumbnailView.mDimAlphaMultiplier; + } + }; + private final float mCornerRadius; private final BaseActivity mActivity; @@ -62,6 +80,7 @@ public class TaskThumbnailView extends View { protected BitmapShader mBitmapShader; private float mDimAlpha = 1f; + private float mDimAlphaMultiplier = 1f; public TaskThumbnailView(Context context) { this(context, null); @@ -109,8 +128,15 @@ public class TaskThumbnailView extends View { updateThumbnailPaintFilter(); } + public void setDimAlphaMultipler(float dimAlphaMultipler) { + mDimAlphaMultiplier = dimAlphaMultipler; + setDimAlpha(mDimAlpha); + } + /** * Sets the alpha of the dim layer on top of this view. + * + * If dimAlpha is 0, no dimming is applied; if dimAlpha is 1, the thumbnail will be black. */ public void setDimAlpha(float dimAlpha) { mDimAlpha = dimAlpha; @@ -124,32 +150,53 @@ public class TaskThumbnailView extends View { return new Rect(); } + public int getSysUiStatusNavFlags() { + if (mThumbnailData != null) { + int flags = 0; + flags |= (mThumbnailData.systemUiVisibility & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0 + ? SystemUiController.FLAG_LIGHT_STATUS + : SystemUiController.FLAG_DARK_STATUS; + flags |= (mThumbnailData.systemUiVisibility & SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0 + ? SystemUiController.FLAG_LIGHT_NAV + : SystemUiController.FLAG_DARK_NAV; + return flags; + } + return 0; + } + @Override protected void onDraw(Canvas canvas) { - if (mTask == null) { - return; + drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius); + } + + public float getCornerRadius() { + return mCornerRadius; + } + + public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height, + float cornerRadius) { + // Draw the background in all cases, except when the thumbnail data is opaque + final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null + || mThumbnailData == null; + if (drawBackgroundOnly || mClipBottom > 0 || mThumbnailData.isTranslucent) { + canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mBackgroundPaint); + if (drawBackgroundOnly) { + return; + } } - int width = getMeasuredWidth(); - int height = getMeasuredHeight(); - if (mClipBottom > 0 && !mTask.isLocked) { - canvas.save(); - canvas.clipRect(0, 0, width, mClipBottom); - canvas.drawRoundRect(0, 0, width, height, mCornerRadius, mCornerRadius, mPaint); - canvas.restore(); + if (mClipBottom > 0) { canvas.save(); - canvas.clipRect(0, mClipBottom, width, height); - canvas.drawRoundRect(0, 0, width, height, mCornerRadius, mCornerRadius, - mBackgroundPaint); + canvas.clipRect(x, y, width, mClipBottom); + canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint); canvas.restore(); } else { - canvas.drawRoundRect(0, 0, width, height, mCornerRadius, - mCornerRadius, mTask.isLocked ? mBackgroundPaint : mPaint); + canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint); } } private void updateThumbnailPaintFilter() { - int mul = (int) (mDimAlpha * 255); + int mul = (int) ((1 - mDimAlpha * mDimAlphaMultiplier) * 255); if (mBitmapShader != null) { LightingColorFilter filter = getLightingColorFilter(mul); mPaint.setColorFilter(filter); @@ -167,9 +214,9 @@ public class TaskThumbnailView extends View { if (mBitmapShader != null && mThumbnailData != null) { float scale = mThumbnailData.scale; Rect thumbnailInsets = mThumbnailData.insets; - float thumbnailWidth = mThumbnailData.thumbnail.getWidth() - + final float thumbnailWidth = mThumbnailData.thumbnail.getWidth() - (thumbnailInsets.left + thumbnailInsets.right) * scale; - float thumbnailHeight = mThumbnailData.thumbnail.getHeight() - + final float thumbnailHeight = mThumbnailData.thumbnail.getHeight() - (thumbnailInsets.top + thumbnailInsets.bottom) * scale; final float thumbnailScale; @@ -185,7 +232,8 @@ public class TaskThumbnailView extends View { // Rotate the screenshot if not in multi-window mode rotate = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION && configuration.orientation != mThumbnailData.orientation && - !mActivity.isInMultiWindowModeCompat(); + !mActivity.isInMultiWindowModeCompat() && + mThumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN; // Scale the screenshot to always fit the width of the card. thumbnailScale = rotate ? getMeasuredWidth() / thumbnailHeight @@ -216,7 +264,8 @@ public class TaskThumbnailView extends View { mMatrix.postScale(thumbnailScale, thumbnailScale); mBitmapShader.setLocalMatrix(mMatrix); - float bitmapHeight = Math.max(thumbnailHeight * thumbnailScale, 0); + float bitmapHeight = Math.max((rotate ? thumbnailWidth : thumbnailHeight) + * thumbnailScale, 0); if (Math.round(bitmapHeight) < getMeasuredHeight()) { mClipBottom = bitmapHeight; } diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index f04acaf12..82aa45a18 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -16,28 +16,34 @@ package com.android.quickstep.views; +import static android.widget.Toast.LENGTH_SHORT; +import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA_MULTIPLIER; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.app.ActivityOptions; import android.content.Context; import android.content.res.Resources; import android.graphics.Outline; -import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.util.AttributeSet; +import android.util.FloatProperty; +import android.util.Log; +import android.util.Property; import android.view.View; import android.view.ViewOutlineProvider; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; -import android.widget.ImageView; +import android.widget.Toast; import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; -import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; -import com.android.quickstep.RecentsAnimationInterpolator; import com.android.quickstep.TaskSystemShortcut; import com.android.quickstep.TaskUtils; import com.android.quickstep.views.RecentsView.PageCallbacks; @@ -54,6 +60,8 @@ import java.util.function.Consumer; */ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallbacks { + private static final String TAG = TaskView.class.getSimpleName(); + /** A curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */ private static final TimeInterpolator CURVE_INTERPOLATOR = x -> (float) -Math.cos(x * Math.PI) / 2f + .5f; @@ -69,12 +77,28 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback */ private static final float EDGE_SCALE_DOWN_FACTOR = 0.03f; - private static final long SCALE_ICON_DURATION = 120; + public static final long SCALE_ICON_DURATION = 120; + private static final long DIM_ANIM_DURATION = 700; + + public static final Property<TaskView, Float> ZOOM_SCALE = + new FloatProperty<TaskView>("zoomScale") { + @Override + public void setValue(TaskView taskView, float v) { + taskView.setZoomScale(v); + } + + @Override + public Float get(TaskView taskView) { + return taskView.mZoomScale; + } + }; private Task mTask; private TaskThumbnailView mSnapshotView; - private ImageView mIconView; + private IconView mIconView; private float mCurveScale; + private float mZoomScale; + private Animator mDimAlphaAnim; public TaskView(Context context) { this(context, null); @@ -87,11 +111,13 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback public TaskView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setOnClickListener((view) -> { - if (mTask != null) { - launchTask(true /* animate */); - BaseActivity.fromContext(context).getUserEventDispatcher().logTaskLaunchOrDismiss( - Touch.TAP, Direction.NONE, TaskUtils.getComponentKeyForTask(mTask.key)); + if (getTask() == null) { + return; } + launchTask(true /* animate */); + BaseActivity.fromContext(context).getUserEventDispatcher().logTaskLaunchOrDismiss( + Touch.TAP, Direction.NONE, ((RecentsView) getParent()).indexOfChild(this), + TaskUtils.getComponentKeyForTask(getTask().key)); }); setOutlineProvider(new TaskOutlineProvider(getResources())); } @@ -124,8 +150,16 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback return mSnapshotView; } + public IconView getIconView() { + return mIconView; + } + public void launchTask(boolean animate) { - launchTask(animate, null, null); + launchTask(animate, (result) -> { + if (!result) { + notifyTaskLaunchFailed(TAG); + } + }, getHandler()); } public void launchTask(boolean animate, Consumer<Boolean> resultCallback, @@ -146,7 +180,7 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback @Override public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) { mSnapshotView.setThumbnail(task, thumbnailData); - mIconView.setImageDrawable(task.icon); + mIconView.setDrawable(task.icon); mIconView.setOnClickListener(icon -> TaskMenuView.showForTask(this)); mIconView.setOnLongClickListener(icon -> { requestDisallowInterceptTouchEvent(true); @@ -157,7 +191,7 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback @Override public void onTaskDataUnloaded() { mSnapshotView.setThumbnail(null, null); - mIconView.setImageDrawable(null); + mIconView.setDrawable(null); mIconView.setOnLongClickListener(null); } @@ -166,23 +200,37 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback // Do nothing } - public void animateIconToScale(float scale) { + public void animateIconToScaleAndDim(float scale) { mIconView.animate().scaleX(scale).scaleY(scale).setDuration(SCALE_ICON_DURATION).start(); + mDimAlphaAnim = ObjectAnimator.ofFloat(mSnapshotView, DIM_ALPHA_MULTIPLIER, 1 - scale, + scale); + mDimAlphaAnim.setDuration(DIM_ANIM_DURATION); + mDimAlphaAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mDimAlphaAnim = null; + } + }); + mDimAlphaAnim.start(); } - protected void setIconScale(float iconScale) { + protected void setIconScaleAndDim(float iconScale) { mIconView.animate().cancel(); mIconView.setScaleX(iconScale); mIconView.setScaleY(iconScale); + if (mDimAlphaAnim != null) { + mDimAlphaAnim.cancel(); + } + mSnapshotView.setDimAlphaMultipler(iconScale); } public void resetVisualProperties() { - setScaleX(1f); - setScaleY(1f); + setZoomScale(1); setTranslationX(0f); setTranslationY(0f); setTranslationZ(0); setAlpha(1f); + setIconScaleAndDim(1); } @Override @@ -190,40 +238,52 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback float curveInterpolation = CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation); - mSnapshotView.setDimAlpha(1 - curveInterpolation * MAX_PAGE_SCRIM_ALPHA); + mSnapshotView.setDimAlpha(curveInterpolation * MAX_PAGE_SCRIM_ALPHA); + setCurveScale(getCurveScaleForCurveInterpolation(curveInterpolation)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + setPivotX((right - left) * 0.5f); + setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f); + } + + public float getCurveScaleForInterpolation(float linearInterpolation) { + float curveInterpolation = CURVE_INTERPOLATOR.getInterpolation(linearInterpolation); + return getCurveScaleForCurveInterpolation(curveInterpolation); + } + + private float getCurveScaleForCurveInterpolation(float curveInterpolation) { + return 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR; + } - mCurveScale = 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR; - setScaleX(mCurveScale); - setScaleY(mCurveScale); + private void setCurveScale(float curveScale) { + mCurveScale = curveScale; + onScaleChanged(); } public float getCurveScale() { return mCurveScale; } + public void setZoomScale(float adjacentScale) { + mZoomScale = adjacentScale; + onScaleChanged(); + } + + private void onScaleChanged() { + float scale = mCurveScale * mZoomScale; + setScaleX(scale); + setScaleY(scale); + } + @Override public boolean hasOverlappingRendering() { // TODO: Clip-out the icon region from the thumbnail, since they are overlapping. return false; } - public RecentsAnimationInterpolator getRecentsInterpolator() { - Rect taskViewBounds = new Rect(); - BaseDraggingActivity activity = BaseDraggingActivity.fromContext(getContext()); - DeviceProfile dp = activity.getDeviceProfile(); - activity.getDragLayer().getDescendantRectRelativeToSelf(this, taskViewBounds); - - // TODO: Use the actual target insets instead of the current thumbnail insets in case the - // device state has changed - return new RecentsAnimationInterpolator( - new Rect(0, 0, dp.widthPx, dp.heightPx), - getThumbnail().getInsets(), - taskViewBounds, - new Rect(0, getThumbnail().getTop(), 0, 0), - getScaleX(), - getTranslationX()); - } - private static final class TaskOutlineProvider extends ViewOutlineProvider { private final int mMarginTop; @@ -281,4 +341,13 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback return super.performAccessibilityAction(action, arguments); } + + public void notifyTaskLaunchFailed(String tag) { + String msg = "Failed to launch task"; + if (mTask != null) { + msg += " (task=" + mTask.key.baseIntent + " userId=" + mTask.key.userId + ")"; + } + Log.w(tag, msg); + Toast.makeText(getContext(), R.string.activity_not_available, LENGTH_SHORT).show(); + } } |