/* * 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.ALL_APPS_HEADER_EXTRA; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.RECENTS_CLEAR_ALL_BUTTON; import static com.android.launcher3.LauncherState.SPRING_LOADED; import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN; import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.os.Build; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Hotseat; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager.StateListener; import com.android.launcher3.R; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.appprediction.PredictionUiStateManager.Client; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.views.ScrimView; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.util.ClipAnimationHelper.TransformParams; import com.android.quickstep.util.LayoutUtils; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.RecentsExtraCard; /** * {@link RecentsView} used in Launcher activity */ @TargetApi(Build.VERSION_CODES.O) public class LauncherRecentsView extends RecentsView implements StateListener { private static final Rect sTempRect = new Rect(); private final TransformParams mTransformParams = new TransformParams(); private RecentsExtraCard mRecentsExtraCardPlugin; private RecentsExtraViewContainer mRecentsExtraViewContainer; private PluginListener mRecentsExtraCardPluginListener = new PluginListener() { @Override public void onPluginConnected(RecentsExtraCard recentsExtraCard, Context context) { createRecentsExtraCard(); mRecentsExtraCardPlugin = recentsExtraCard; mRecentsExtraCardPlugin.setupView(context, mRecentsExtraViewContainer, mActivity); } @Override public void onPluginDisconnected(RecentsExtraCard plugin) { removeView(mRecentsExtraViewContainer); mRecentsExtraCardPlugin = null; mRecentsExtraViewContainer = null; } }; public LauncherRecentsView(Context context) { this(context, null); } public LauncherRecentsView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setContentAlpha(0); mActivity.getStateManager().addStateListener(this); } @Override public void startHome() { mActivity.getStateManager().goToState(NORMAL); } @Override public void setTranslationY(float translationY) { super.setTranslationY(translationY); if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { LauncherState state = mActivity.getStateManager().getState(); if (state == OVERVIEW || state == ALL_APPS) { redrawLiveTile(false); } } } @Override public void draw(Canvas canvas) { maybeDrawEmptyMessage(canvas); super.draw(canvas); } @Override public void onViewAdded(View child) { super.onViewAdded(child); updateEmptyMessage(); } @Override protected void onTaskStackUpdated() { // Lazily update the empty message only when the task stack is reapplied updateEmptyMessage(); } /** * Animates adjacent tasks and translate hotseat off screen as well. */ @Override public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv, ClipAnimationHelper helper) { AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv, helper); if (!SysUINavigationMode.getMode(mActivity).hasGestures) { // 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(); if ((state.getVisibleElements(mActivity) & ALL_APPS_HEADER_EXTRA) != 0) { float maxShiftRange = mActivity.getDeviceProfile().heightPx; float currShiftRange = mActivity.getAllAppsController().getShiftRange(); allAppsProgressOffscreen = 1f + (maxShiftRange - currShiftRange) / maxShiftRange; } anim.play(ObjectAnimator.ofFloat( mActivity.getAllAppsController(), ALL_APPS_PROGRESS, allAppsProgressOffscreen)); ObjectAnimator dragHandleAnim = ObjectAnimator.ofInt( mActivity.findViewById(R.id.scrim_view), ScrimView.DRAG_HANDLE_ALPHA, 0); dragHandleAnim.setInterpolator(Interpolators.ACCEL_2); anim.play(dragHandleAnim); return anim; } @Override protected void getTaskSize(DeviceProfile dp, Rect outRect) { LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect); } /** * @return The translationX to apply to this view so that the first task is just offscreen. */ public float getOffscreenTranslationX(float recentsScale) { float offscreenX = NORMAL.getOverviewScaleAndTranslation(mActivity).translationX; // Offset since scale pushes tasks outwards. getTaskSize(sTempRect); int taskWidth = sTempRect.width(); offscreenX += taskWidth * (recentsScale - 1) / 2; if (mRunningTaskTileHidden) { // The first task is hidden, so offset by its width. offscreenX -= (taskWidth + getPageSpacing()) * recentsScale; } if (isRtl()) { offscreenX = -offscreenX; } return offscreenX; } @Override protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { if (mRecentsAnimationWrapper.targetSet != null && tv.isRunningTask()) { mTransformParams.setProgress(1 - progress) .setSyncTransactionApplier(mSyncTransactionApplier) .setForLiveTile(true); mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet, mTransformParams); } else { redrawLiveTile(true); } } } @Override protected void onTaskLaunched(boolean success) { if (success) { mActivity.getStateManager().goToState(NORMAL, false /* animate */); } else { LauncherState state = mActivity.getStateManager().getState(); mActivity.getAllAppsController().setState(state); } super.onTaskLaunched(success); } @Override public boolean shouldUseMultiWindowTaskSizeStrategy() { return mActivity.isInMultiWindowMode(); } @Override public void scrollTo(int x, int y) { super.scrollTo(x, y); if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) { redrawLiveTile(true); } } @Override public void redrawLiveTile(boolean mightNeedToRefill) { if (!mEnableDrawingLiveTile || mRecentsAnimationWrapper == null || mClipAnimationHelper == null) { return; } TaskView taskView = getRunningTaskView(); if (taskView != null) { taskView.getThumbnail().getGlobalVisibleRect(mTempRect); int offsetX = (int) (mTaskWidth * taskView.getScaleX() * getScaleX() - mTempRect.width()); int offsetY = (int) (mTaskHeight * taskView.getScaleY() * getScaleY() - mTempRect.height()); if (((mCurrentPage != 0) || mightNeedToRefill) && offsetX > 0) { if (mTempRect.left - offsetX < 0) { mTempRect.left -= offsetX; } else { mTempRect.right += offsetX; } } if (mightNeedToRefill && offsetY > 0) { mTempRect.top -= offsetY; } mTempRectF.set(mTempRect); mTransformParams.setProgress(1f) .setCurrentRectAndTargetAlpha(mTempRectF, taskView.getAlpha()) .setSyncTransactionApplier(mSyncTransactionApplier); if (mRecentsAnimationWrapper.targetSet != null) { mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet, mTransformParams); } } } @Override public void reset() { super.reset(); // We are moving to home or some other UI with no recents. Switch back to the home client, // the home predictions should have been updated when the activity was resumed. PredictionUiStateManager.INSTANCE.get(getContext()).switchClient(Client.HOME); } @Override public void onStateTransitionStart(LauncherState toState) { setOverviewStateEnabled(toState.overviewUi); setFreezeViewVisibility(true); } @Override public void onStateTransitionComplete(LauncherState finalState) { if (finalState == NORMAL || finalState == SPRING_LOADED) { // Clean-up logic that occurs when recents is no longer in use/visible. reset(); } setOverlayEnabled(finalState == OVERVIEW); setFreezeViewVisibility(false); } @Override public void setOverviewStateEnabled(boolean enabled) { super.setOverviewStateEnabled(enabled); if (enabled) { LauncherState state = mActivity.getStateManager().getState(); boolean hasClearAllButton = (state.getVisibleElements(mActivity) & RECENTS_CLEAR_ALL_BUTTON) != 0; setDisallowScrollToClearAll(!hasClearAllButton); } } @Override protected boolean shouldStealTouchFromSiblingsBelow(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { // Allow touches to go through to the hotseat. Hotseat hotseat = mActivity.getHotseat(); boolean touchingHotseat = hotseat.isShown() && mActivity.getDragLayer().isEventOverView(hotseat, ev, this); return !touchingHotseat; } return super.shouldStealTouchFromSiblingsBelow(ev); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); PluginManagerWrapper.INSTANCE.get(getContext()) .addPluginListener(mRecentsExtraCardPluginListener, RecentsExtraCard.class); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener( mRecentsExtraCardPluginListener); } @Override protected int computeMinScrollX() { if (canComputeScrollX() && !mIsRtl) { return computeScrollX(); } return super.computeMinScrollX(); } @Override protected int computeMaxScrollX() { if (canComputeScrollX() && mIsRtl) { return computeScrollX(); } return super.computeMaxScrollX(); } private boolean canComputeScrollX() { return mRecentsExtraCardPlugin != null && getTaskViewCount() > 0 && !mDisallowScrollToClearAll; } private int computeScrollX() { int scrollIndex = getTaskViewStartIndex() - 1; while (scrollIndex >= 0 && getChildAt(scrollIndex) instanceof RecentsExtraViewContainer && ((RecentsExtraViewContainer) getChildAt(scrollIndex)).isScrollable()) { scrollIndex--; } return getScrollForPage(scrollIndex + 1); } private void createRecentsExtraCard() { mRecentsExtraViewContainer = new RecentsExtraViewContainer(getContext()); FrameLayout.LayoutParams helpCardParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); mRecentsExtraViewContainer.setLayoutParams(helpCardParams); mRecentsExtraViewContainer.setScrollable(true); addView(mRecentsExtraViewContainer, 0); } @Override public void resetTaskVisuals() { super.resetTaskVisuals(); if (mRecentsExtraViewContainer != null) { mRecentsExtraViewContainer.setAlpha(mContentAlpha); } } }