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