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