• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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