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