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