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 android.animation.Animator; 20 import android.animation.AnimatorSet; 21 import android.animation.ObjectAnimator; 22 import android.content.Context; 23 import android.graphics.Outline; 24 import android.graphics.Point; 25 import android.graphics.Rect; 26 import android.graphics.drawable.Drawable; 27 import android.util.AttributeSet; 28 import android.view.MotionEvent; 29 import android.view.View; 30 import android.view.ViewOutlineProvider; 31 import android.view.animation.AccelerateDecelerateInterpolator; 32 import android.widget.TextView; 33 34 import com.android.launcher3.AbstractFloatingView; 35 import com.android.launcher3.BaseDraggingActivity; 36 import com.android.launcher3.LauncherAnimUtils; 37 import com.android.launcher3.R; 38 import com.android.launcher3.Utilities; 39 import com.android.launcher3.anim.AnimationSuccessListener; 40 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; 41 import com.android.launcher3.shortcuts.DeepShortcutView; 42 import com.android.launcher3.views.BaseDragLayer; 43 import com.android.quickstep.TaskSystemShortcut; 44 import com.android.quickstep.TaskUtils; 45 46 /** 47 * Contains options for a recent task when long-pressing its icon. 48 */ 49 public class TaskMenuView extends AbstractFloatingView { 50 51 private static final Rect sTempRect = new Rect(); 52 53 /** Note that these will be shown in order from top to bottom, if available for the task. */ 54 public static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[] { 55 new TaskSystemShortcut.AppInfo(), 56 new TaskSystemShortcut.SplitScreen(), 57 new TaskSystemShortcut.Pin(), 58 new TaskSystemShortcut.Install(), 59 }; 60 61 private static final long OPEN_CLOSE_DURATION = 220; 62 63 private BaseDraggingActivity mActivity; 64 private TextView mTaskIconAndName; 65 private AnimatorSet mOpenCloseAnimator; 66 private TaskView mTaskView; 67 TaskMenuView(Context context, AttributeSet attrs)68 public TaskMenuView(Context context, AttributeSet attrs) { 69 this(context, attrs, 0); 70 } 71 TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr)72 public TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr) { 73 super(context, attrs, defStyleAttr); 74 75 mActivity = BaseDraggingActivity.fromContext(context); 76 setClipToOutline(true); 77 setOutlineProvider(new ViewOutlineProvider() { 78 @Override 79 public void getOutline(View view, Outline outline) { 80 float r = getResources().getDimensionPixelSize(R.dimen.task_menu_background_radius); 81 outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), r); 82 } 83 }); 84 } 85 86 @Override onFinishInflate()87 protected void onFinishInflate() { 88 super.onFinishInflate(); 89 mTaskIconAndName = findViewById(R.id.task_icon_and_name); 90 } 91 92 @Override onControllerInterceptTouchEvent(MotionEvent ev)93 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 94 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 95 BaseDragLayer dl = mActivity.getDragLayer(); 96 if (!dl.isEventOverView(this, ev)) { 97 // TODO: log this once we have a new container type for it? 98 close(true); 99 return true; 100 } 101 } 102 return false; 103 } 104 105 @Override handleClose(boolean animate)106 protected void handleClose(boolean animate) { 107 if (animate) { 108 animateClose(); 109 } else { 110 closeComplete(); 111 } 112 } 113 114 @Override logActionCommand(int command)115 public void logActionCommand(int command) { 116 // TODO 117 } 118 119 @Override isOfType(int type)120 protected boolean isOfType(int type) { 121 return (type & TYPE_TASK_MENU) != 0; 122 } 123 showForTask(TaskView taskView)124 public static boolean showForTask(TaskView taskView) { 125 BaseDraggingActivity activity = BaseDraggingActivity.fromContext(taskView.getContext()); 126 final TaskMenuView taskMenuView = (TaskMenuView) activity.getLayoutInflater().inflate( 127 R.layout.task_menu, activity.getDragLayer(), false); 128 return taskMenuView.populateAndShowForTask(taskView); 129 } 130 populateAndShowForTask(TaskView taskView)131 private boolean populateAndShowForTask(TaskView taskView) { 132 if (isAttachedToWindow()) { 133 return false; 134 } 135 mActivity.getDragLayer().addView(this); 136 mTaskView = taskView; 137 addMenuOptions(mTaskView); 138 orientAroundTaskView(mTaskView); 139 post(this::animateOpen); 140 return true; 141 } 142 addMenuOptions(TaskView taskView)143 private void addMenuOptions(TaskView taskView) { 144 Drawable icon = taskView.getTask().icon.getConstantState().newDrawable(); 145 int iconSize = getResources().getDimensionPixelSize(R.dimen.task_thumbnail_icon_size); 146 icon.setBounds(0, 0, iconSize, iconSize); 147 mTaskIconAndName.setCompoundDrawables(null, icon, null, null); 148 mTaskIconAndName.setText(TaskUtils.getTitle(getContext(), taskView.getTask())); 149 mTaskIconAndName.setOnClickListener(v -> close(true)); 150 151 for (TaskSystemShortcut menuOption : MENU_OPTIONS) { 152 OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, taskView); 153 if (onClickListener != null) { 154 addMenuOption(menuOption, onClickListener); 155 } 156 } 157 } 158 addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener)159 private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) { 160 DeepShortcutView menuOptionView = (DeepShortcutView) mActivity.getLayoutInflater().inflate( 161 R.layout.system_shortcut, this, false); 162 menuOptionView.getIconView().setBackgroundResource(menuOption.iconResId); 163 menuOptionView.getBubbleText().setText(menuOption.labelResId); 164 menuOptionView.setOnClickListener(onClickListener); 165 addView(menuOptionView); 166 } 167 orientAroundTaskView(TaskView taskView)168 private void orientAroundTaskView(TaskView taskView) { 169 measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 170 mActivity.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect); 171 Rect insets = mActivity.getDragLayer().getInsets(); 172 int x = sTempRect.left + (sTempRect.width() - getMeasuredWidth()) / 2 - insets.left; 173 setX(Utilities.isRtl(getResources()) ? -x : x); 174 setY(sTempRect.top - mTaskIconAndName.getPaddingTop() - insets.top); 175 } 176 animateOpen()177 private void animateOpen() { 178 animateOpenOrClosed(false); 179 mIsOpen = true; 180 } 181 animateClose()182 private void animateClose() { 183 animateOpenOrClosed(true); 184 } 185 animateOpenOrClosed(boolean closing)186 private void animateOpenOrClosed(boolean closing) { 187 if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) { 188 return; 189 } 190 mOpenCloseAnimator = LauncherAnimUtils.createAnimatorSet(); 191 mOpenCloseAnimator.play(createOpenCloseOutlineProvider() 192 .createRevealAnimator(this, closing)); 193 mOpenCloseAnimator.addListener(new AnimationSuccessListener() { 194 @Override 195 public void onAnimationStart(Animator animation) { 196 setVisibility(VISIBLE); 197 } 198 199 @Override 200 public void onAnimationSuccess(Animator animator) { 201 if (closing) { 202 closeComplete(); 203 } 204 } 205 }); 206 mOpenCloseAnimator.play(ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1)); 207 mOpenCloseAnimator.setDuration(OPEN_CLOSE_DURATION); 208 mOpenCloseAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); 209 mOpenCloseAnimator.start(); 210 } 211 closeComplete()212 private void closeComplete() { 213 mIsOpen = false; 214 mActivity.getDragLayer().removeView(this); 215 } 216 createOpenCloseOutlineProvider()217 private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() { 218 int iconSize = getResources().getDimensionPixelSize(R.dimen.task_thumbnail_icon_size); 219 float fromRadius = iconSize / 2; 220 float toRadius = getResources().getDimensionPixelSize( 221 R.dimen.task_menu_background_radius); 222 Point iconCenter = new Point(getWidth() / 2, mTaskIconAndName.getPaddingTop() + iconSize / 2); 223 Rect fromRect = new Rect(iconCenter.x, iconCenter.y, iconCenter.x, iconCenter.y); 224 Rect toRect = new Rect(0, 0, getWidth(), getHeight()); 225 return new RoundedRectRevealOutlineProvider(fromRadius, toRadius, fromRect, toRect) { 226 @Override 227 public boolean shouldRemoveElevationDuringAnimation() { 228 return true; 229 } 230 }; 231 } 232 } 233