• 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 static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorSet;
23 import android.animation.ObjectAnimator;
24 import android.content.Context;
25 import android.graphics.Outline;
26 import android.graphics.Rect;
27 import android.graphics.drawable.ShapeDrawable;
28 import android.graphics.drawable.shapes.RectShape;
29 import android.util.AttributeSet;
30 import android.view.Gravity;
31 import android.view.MotionEvent;
32 import android.view.View;
33 import android.view.ViewOutlineProvider;
34 import android.widget.LinearLayout;
35 import android.widget.TextView;
36 
37 import androidx.annotation.Nullable;
38 
39 import com.android.launcher3.AbstractFloatingView;
40 import com.android.launcher3.BaseDraggingActivity;
41 import com.android.launcher3.DeviceProfile;
42 import com.android.launcher3.R;
43 import com.android.launcher3.anim.AnimationSuccessListener;
44 import com.android.launcher3.anim.Interpolators;
45 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
46 import com.android.launcher3.popup.SystemShortcut;
47 import com.android.launcher3.touch.PagedOrientationHandler;
48 import com.android.launcher3.views.BaseDragLayer;
49 import com.android.quickstep.TaskOverlayFactory;
50 import com.android.quickstep.TaskUtils;
51 import com.android.quickstep.util.TaskCornerRadius;
52 import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
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 static final int REVEAL_OPEN_DURATION = 150;
62     private static final int REVEAL_CLOSE_DURATION = 100;
63 
64     private BaseDraggingActivity mActivity;
65     private TextView mTaskName;
66     @Nullable
67     private AnimatorSet mOpenCloseAnimator;
68     private TaskView mTaskView;
69     private TaskIdAttributeContainer mTaskContainer;
70     private LinearLayout mOptionLayout;
71 
TaskMenuView(Context context, AttributeSet attrs)72     public TaskMenuView(Context context, AttributeSet attrs) {
73         this(context, attrs, 0);
74     }
75 
TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr)76     public TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
77         super(context, attrs, defStyleAttr);
78 
79         mActivity = BaseDraggingActivity.fromContext(context);
80         setClipToOutline(true);
81     }
82 
83     @Override
onFinishInflate()84     protected void onFinishInflate() {
85         super.onFinishInflate();
86         mTaskName = findViewById(R.id.task_name);
87         mOptionLayout = findViewById(R.id.menu_option_layout);
88     }
89 
90     @Override
onControllerInterceptTouchEvent(MotionEvent ev)91     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
92         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
93             BaseDragLayer dl = mActivity.getDragLayer();
94             if (!dl.isEventOverView(this, ev)) {
95                 // TODO: log this once we have a new container type for it?
96                 close(true);
97                 return true;
98             }
99         }
100         return false;
101     }
102 
103     @Override
handleClose(boolean animate)104     protected void handleClose(boolean animate) {
105         if (animate) {
106             animateClose();
107         } else {
108             closeComplete();
109         }
110     }
111 
112     @Override
isOfType(int type)113     protected boolean isOfType(int type) {
114         return (type & TYPE_TASK_MENU) != 0;
115     }
116 
117     @Override
getOutlineProvider()118     public ViewOutlineProvider getOutlineProvider() {
119         return new ViewOutlineProvider() {
120             @Override
121             public void getOutline(View view, Outline outline) {
122                 outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(),
123                         TaskCornerRadius.get(view.getContext()));
124             }
125         };
126     }
127 
128     public void onRotationChanged() {
129         if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
130             mOpenCloseAnimator.end();
131         }
132         if (mIsOpen) {
133             mOptionLayout.removeAllViews();
134             if (!populateAndLayoutMenu()) {
135                 close(false);
136             }
137         }
138     }
139 
140     public static boolean showForTask(TaskIdAttributeContainer taskContainer) {
141         BaseDraggingActivity activity = BaseDraggingActivity.fromContext(
142                 taskContainer.getTaskView().getContext());
143         final TaskMenuView taskMenuView = (TaskMenuView) activity.getLayoutInflater().inflate(
144                         R.layout.task_menu, activity.getDragLayer(), false);
145         return taskMenuView.populateAndShowForTask(taskContainer);
146     }
147 
148     private boolean populateAndShowForTask(TaskIdAttributeContainer taskContainer) {
149         if (isAttachedToWindow()) {
150             return false;
151         }
152         mActivity.getDragLayer().addView(this);
153         mTaskView = taskContainer.getTaskView();
154         mTaskContainer = taskContainer;
155         if (!populateAndLayoutMenu()) {
156             return false;
157         }
158         post(this::animateOpen);
159         return true;
160     }
161 
162     /** @return true if successfully able to populate task view menu, false otherwise */
163     private boolean populateAndLayoutMenu() {
164         if (mTaskContainer.getTask().icon == null) {
165             // Icon may not be loaded
166             return false;
167         }
168         addMenuOptions(mTaskContainer);
169         orientAroundTaskView(mTaskContainer);
170         return true;
171     }
172 
173     private void addMenuOptions(TaskIdAttributeContainer taskContainer) {
174         mTaskName.setText(TaskUtils.getTitle(getContext(), taskContainer.getTask()));
175         mTaskName.setOnClickListener(v -> close(true));
176         TaskOverlayFactory.getEnabledShortcuts(mTaskView, taskContainer)
177                 .forEach(this::addMenuOption);
178     }
179 
180     private void addMenuOption(SystemShortcut menuOption) {
181         LinearLayout menuOptionView = (LinearLayout) mActivity.getLayoutInflater().inflate(
182                 R.layout.task_view_menu_option, this, false);
183         menuOption.setIconAndLabelFor(
184                 menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
185         LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
186         mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp,
187                 menuOptionView, mActivity.getDeviceProfile());
188         // Set an onClick listener on each menu option. The onClick method is responsible for
189         // ending LiveTile mode on the thumbnail if needed.
190         menuOptionView.setOnClickListener(menuOption::onClick);
191         mOptionLayout.addView(menuOptionView);
192     }
193 
194     private void orientAroundTaskView(TaskIdAttributeContainer taskContainer) {
195         RecentsView recentsView = mActivity.getOverviewPanel();
196         PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
197         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
198 
199         // Get Position
200         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
201         mActivity.getDragLayer().getDescendantRectRelativeToSelf(taskContainer.getThumbnailView(),
202                 sTempRect);
203         Rect insets = mActivity.getDragLayer().getInsets();
204         BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
205         int padding = getResources()
206                 .getDimensionPixelSize(R.dimen.task_menu_vertical_padding);
207         params.width = orientationHandler
208                 .getTaskMenuWidth(taskContainer.getThumbnailView(),
209                         deviceProfile, taskContainer.getStagePosition()) - (2 * padding);
210         // Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
211         params.gravity = Gravity.LEFT;
212         setLayoutParams(params);
213         setScaleX(mTaskView.getScaleX());
214         setScaleY(mTaskView.getScaleY());
215 
216         // Set divider spacing
217         ShapeDrawable divider = new ShapeDrawable(new RectShape());
218         divider.getPaint().setColor(getResources().getColor(android.R.color.transparent));
219         int dividerSpacing = (int) getResources().getDimension(R.dimen.task_menu_spacing);
220         mOptionLayout.setShowDividers(SHOW_DIVIDER_MIDDLE);
221 
222         orientationHandler.setTaskOptionsMenuLayoutOrientation(
223                 deviceProfile, mOptionLayout, dividerSpacing, divider);
224         float thumbnailAlignedX = sTempRect.left - insets.left;
225         float thumbnailAlignedY = sTempRect.top - insets.top;
226         // Changing pivot to make computations easier
227         // NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set,
228         // which would render the X and Y position set here incorrect
229         setPivotX(0);
230         setPivotY(0);
231         setRotation(orientationHandler.getDegreesRotated());
232 
233         // Margin that insets the menuView inside the taskView
234         float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin);
235         setTranslationX(orientationHandler.getTaskMenuX(thumbnailAlignedX,
236                 mTaskContainer.getThumbnailView(), deviceProfile, taskInsetMargin));
237         setTranslationY(orientationHandler.getTaskMenuY(
238                 thumbnailAlignedY, mTaskContainer.getThumbnailView(),
239                 mTaskContainer.getStagePosition(), this, taskInsetMargin));
240     }
241 
242     private void animateOpen() {
243         animateOpenOrClosed(false);
244         mIsOpen = true;
245     }
246 
247     private void animateClose() {
248         animateOpenOrClosed(true);
249     }
250 
251     private void animateOpenOrClosed(boolean closing) {
252         if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
253             mOpenCloseAnimator.end();
254         }
255         mOpenCloseAnimator = new AnimatorSet();
256 
257         final Animator revealAnimator = createOpenCloseOutlineProvider()
258                 .createRevealAnimator(this, closing);
259         revealAnimator.setInterpolator(Interpolators.DEACCEL);
260         mOpenCloseAnimator.playTogether(revealAnimator,
261                 ObjectAnimator.ofFloat(
262                         mTaskContainer.getThumbnailView(), DIM_ALPHA,
263                         closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA),
264                 ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1));
265         mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
266             @Override
267             public void onAnimationStart(Animator animation) {
268                 setVisibility(VISIBLE);
269             }
270 
271             @Override
272             public void onAnimationSuccess(Animator animator) {
273                 if (closing) {
274                     closeComplete();
275                 }
276             }
277         });
278         mOpenCloseAnimator.setDuration(closing ? REVEAL_CLOSE_DURATION: REVEAL_OPEN_DURATION);
279         mOpenCloseAnimator.start();
280     }
281 
282     private void closeComplete() {
283         mIsOpen = false;
284         mActivity.getDragLayer().removeView(this);
285     }
286 
287     private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
288         float radius = TaskCornerRadius.get(mContext);
289         Rect fromRect = new Rect(0, 0, getWidth(), 0);
290         Rect toRect = new Rect(0, 0, getWidth(), getHeight());
291         return new RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect);
292     }
293 
294 }
295