1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.quickstep.views; 18 19 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; 20 import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA; 21 22 import android.animation.Animator; 23 import android.animation.AnimatorSet; 24 import android.animation.ObjectAnimator; 25 import android.content.Context; 26 import android.graphics.Rect; 27 import android.graphics.drawable.Drawable; 28 import android.util.AttributeSet; 29 import android.view.Gravity; 30 import android.view.MotionEvent; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.widget.LinearLayout; 34 import android.widget.TextView; 35 36 import com.android.launcher3.AbstractFloatingView; 37 import com.android.launcher3.BaseDraggingActivity; 38 import com.android.launcher3.FastBitmapDrawable; 39 import com.android.launcher3.R; 40 import com.android.launcher3.anim.AnimationSuccessListener; 41 import com.android.launcher3.anim.Interpolators; 42 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; 43 import com.android.launcher3.util.Themes; 44 import com.android.launcher3.views.BaseDragLayer; 45 import com.android.quickstep.TaskOverlayFactory; 46 import com.android.quickstep.TaskSystemShortcut; 47 import com.android.quickstep.TaskUtils; 48 import com.android.quickstep.views.IconView.OnScaleUpdateListener; 49 50 import java.util.List; 51 52 /** 53 * Contains options for a recent task when long-pressing its icon. 54 */ 55 public class TaskMenuView extends AbstractFloatingView { 56 57 private static final Rect sTempRect = new Rect(); 58 59 private final OnScaleUpdateListener mTaskViewIconScaleListener = new OnScaleUpdateListener() { 60 @Override 61 public void onScaleUpdate(float scale) { 62 final Drawable drawable = mTaskIcon.getDrawable(); 63 if (drawable instanceof FastBitmapDrawable) { 64 if (scale != ((FastBitmapDrawable) drawable).getScale()) { 65 mMenuIconDrawable.setScale(scale); 66 } 67 } 68 } 69 }; 70 71 private final OnScaleUpdateListener mMenuIconScaleListener = new OnScaleUpdateListener() { 72 @Override 73 public void onScaleUpdate(float scale) { 74 final Drawable taskViewDrawable = mTaskView.getIconView().getDrawable(); 75 if (taskViewDrawable instanceof FastBitmapDrawable) { 76 final float currentScale = ((FastBitmapDrawable) taskViewDrawable).getScale(); 77 if (currentScale != scale) { 78 ((FastBitmapDrawable) taskViewDrawable).setScale(scale); 79 } 80 } 81 } 82 }; 83 84 private static final int REVEAL_OPEN_DURATION = 150; 85 private static final int REVEAL_CLOSE_DURATION = 100; 86 87 private final float mThumbnailTopMargin; 88 private BaseDraggingActivity mActivity; 89 private TextView mTaskName; 90 private IconView mTaskIcon; 91 private AnimatorSet mOpenCloseAnimator; 92 private TaskView mTaskView; 93 private LinearLayout mOptionLayout; 94 private FastBitmapDrawable mMenuIconDrawable; 95 TaskMenuView(Context context, AttributeSet attrs)96 public TaskMenuView(Context context, AttributeSet attrs) { 97 this(context, attrs, 0); 98 } 99 TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr)100 public TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr) { 101 super(context, attrs, defStyleAttr); 102 103 mActivity = BaseDraggingActivity.fromContext(context); 104 mThumbnailTopMargin = getResources().getDimension(R.dimen.task_thumbnail_top_margin); 105 } 106 107 @Override onFinishInflate()108 protected void onFinishInflate() { 109 super.onFinishInflate(); 110 mTaskName = findViewById(R.id.task_name); 111 mTaskIcon = findViewById(R.id.task_icon); 112 mOptionLayout = findViewById(R.id.menu_option_layout); 113 } 114 115 @Override onControllerInterceptTouchEvent(MotionEvent ev)116 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 117 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 118 BaseDragLayer dl = mActivity.getDragLayer(); 119 if (!dl.isEventOverView(this, ev)) { 120 // TODO: log this once we have a new container type for it? 121 close(true); 122 return true; 123 } 124 } 125 return false; 126 } 127 128 @Override handleClose(boolean animate)129 protected void handleClose(boolean animate) { 130 if (animate) { 131 animateClose(); 132 } else { 133 closeComplete(); 134 } 135 } 136 137 @Override logActionCommand(int command)138 public void logActionCommand(int command) { 139 // TODO 140 } 141 142 @Override onDetachedFromWindow()143 protected void onDetachedFromWindow() { 144 super.onDetachedFromWindow(); 145 146 // Remove all scale listeners when menu is removed 147 mTaskView.getIconView().removeUpdateScaleListener(mTaskViewIconScaleListener); 148 mTaskIcon.removeUpdateScaleListener(mMenuIconScaleListener); 149 } 150 151 @Override isOfType(int type)152 protected boolean isOfType(int type) { 153 return (type & TYPE_TASK_MENU) != 0; 154 } 155 setPosition(float x, float y)156 public void setPosition(float x, float y) { 157 setX(x); 158 setY(y + mThumbnailTopMargin); 159 } 160 showForTask(TaskView taskView)161 public static TaskMenuView showForTask(TaskView taskView) { 162 BaseDraggingActivity activity = BaseDraggingActivity.fromContext(taskView.getContext()); 163 final TaskMenuView taskMenuView = (TaskMenuView) activity.getLayoutInflater().inflate( 164 R.layout.task_menu, activity.getDragLayer(), false); 165 return taskMenuView.populateAndShowForTask(taskView) ? taskMenuView : null; 166 } 167 populateAndShowForTask(TaskView taskView)168 private boolean populateAndShowForTask(TaskView taskView) { 169 if (isAttachedToWindow()) { 170 return false; 171 } 172 mActivity.getDragLayer().addView(this); 173 mTaskView = taskView; 174 addMenuOptions(mTaskView); 175 orientAroundTaskView(mTaskView); 176 post(this::animateOpen); 177 return true; 178 } 179 addMenuOptions(TaskView taskView)180 private void addMenuOptions(TaskView taskView) { 181 Drawable icon = taskView.getTask().icon.getConstantState().newDrawable(); 182 mTaskIcon.setDrawable(icon); 183 mTaskIcon.setOnClickListener(v -> close(true)); 184 mTaskName.setText(TaskUtils.getTitle(getContext(), taskView.getTask())); 185 mTaskName.setOnClickListener(v -> close(true)); 186 187 // Set the icons to match scale by listening to each other's changes 188 mMenuIconDrawable = icon instanceof FastBitmapDrawable ? (FastBitmapDrawable) icon : null; 189 taskView.getIconView().addUpdateScaleListener(mTaskViewIconScaleListener); 190 mTaskIcon.addUpdateScaleListener(mMenuIconScaleListener); 191 192 // Move the icon and text up half an icon size to lay over the TaskView 193 LinearLayout.LayoutParams params = 194 (LinearLayout.LayoutParams) mTaskIcon.getLayoutParams(); 195 params.topMargin = (int) -mThumbnailTopMargin; 196 mTaskIcon.setLayoutParams(params); 197 198 final BaseDraggingActivity activity = BaseDraggingActivity.fromContext(getContext()); 199 final List<TaskSystemShortcut> shortcuts = 200 TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(taskView); 201 final int count = shortcuts.size(); 202 for (int i = 0; i < count; ++i) { 203 final TaskSystemShortcut menuOption = shortcuts.get(i); 204 addMenuOption(menuOption, menuOption.getOnClickListener(activity, taskView)); 205 } 206 } 207 addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener)208 private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) { 209 ViewGroup menuOptionView = (ViewGroup) mActivity.getLayoutInflater().inflate( 210 R.layout.task_view_menu_option, this, false); 211 menuOption.setIconAndLabelFor( 212 menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text)); 213 menuOptionView.setOnClickListener(onClickListener); 214 mOptionLayout.addView(menuOptionView); 215 } 216 orientAroundTaskView(TaskView taskView)217 private void orientAroundTaskView(TaskView taskView) { 218 measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 219 mActivity.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect); 220 Rect insets = mActivity.getDragLayer().getInsets(); 221 BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams(); 222 params.width = taskView.getMeasuredWidth(); 223 params.gravity = Gravity.START; 224 setLayoutParams(params); 225 setScaleX(taskView.getScaleX()); 226 setScaleY(taskView.getScaleY()); 227 setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top); 228 } 229 animateOpen()230 private void animateOpen() { 231 animateOpenOrClosed(false); 232 mIsOpen = true; 233 } 234 animateClose()235 private void animateClose() { 236 animateOpenOrClosed(true); 237 } 238 animateOpenOrClosed(boolean closing)239 private void animateOpenOrClosed(boolean closing) { 240 if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) { 241 mOpenCloseAnimator.end(); 242 } 243 mOpenCloseAnimator = new AnimatorSet(); 244 245 final Animator revealAnimator = createOpenCloseOutlineProvider() 246 .createRevealAnimator(this, closing); 247 revealAnimator.setInterpolator(Interpolators.DEACCEL); 248 mOpenCloseAnimator.play(revealAnimator); 249 mOpenCloseAnimator.play(ObjectAnimator.ofFloat(mTaskView.getThumbnail(), DIM_ALPHA, 250 closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA)); 251 mOpenCloseAnimator.addListener(new AnimationSuccessListener() { 252 @Override 253 public void onAnimationStart(Animator animation) { 254 setVisibility(VISIBLE); 255 } 256 257 @Override 258 public void onAnimationSuccess(Animator animator) { 259 if (closing) { 260 closeComplete(); 261 } 262 } 263 }); 264 mOpenCloseAnimator.play(ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1)); 265 mOpenCloseAnimator.setDuration(closing ? REVEAL_CLOSE_DURATION: REVEAL_OPEN_DURATION); 266 mOpenCloseAnimator.start(); 267 } 268 closeComplete()269 private void closeComplete() { 270 mIsOpen = false; 271 mActivity.getDragLayer().removeView(this); 272 } 273 createOpenCloseOutlineProvider()274 private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() { 275 float radius = Themes.getDialogCornerRadius(getContext()); 276 Rect fromRect = new Rect(0, 0, getWidth(), 0); 277 Rect toRect = new Rect(0, 0, getWidth(), getHeight()); 278 return new RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect); 279 } 280 findMenuItemByText(String text)281 public View findMenuItemByText(String text) { 282 for (int i = mOptionLayout.getChildCount() - 1; i >= 0; --i) { 283 final ViewGroup menuOptionView = (ViewGroup) mOptionLayout.getChildAt(i); 284 if (text.equals(menuOptionView.<TextView>findViewById(R.id.text).getText())) { 285 return menuOptionView; 286 } 287 } 288 return null; 289 } 290 } 291