1 /*
2  * Copyright (C) 2020 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.content.Context;
20 import android.content.res.Configuration;
21 import android.graphics.Rect;
22 import android.util.AttributeSet;
23 import android.util.Log;
24 import android.view.View;
25 import android.view.View.OnClickListener;
26 import android.widget.Button;
27 import android.widget.FrameLayout;
28 import android.widget.LinearLayout;
29 
30 import androidx.annotation.IntDef;
31 import androidx.annotation.Nullable;
32 
33 import com.android.launcher3.DeviceProfile;
34 import com.android.launcher3.Flags;
35 import com.android.launcher3.Insettable;
36 import com.android.launcher3.R;
37 import com.android.launcher3.anim.AnimatedFloat;
38 import com.android.launcher3.util.DisplayController;
39 import com.android.launcher3.util.MultiValueAlpha;
40 import com.android.launcher3.util.NavigationMode;
41 import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
42 import com.android.quickstep.util.LayoutUtils;
43 
44 import java.lang.annotation.Retention;
45 import java.lang.annotation.RetentionPolicy;
46 import java.util.Arrays;
47 
48 /**
49  * View for showing action buttons in Overview
50  */
51 public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayout
52         implements OnClickListener, Insettable {
53     public static final String TAG = "OverviewActionsView";
54     private final Rect mInsets = new Rect();
55 
56     @IntDef(flag = true, value = {
57             HIDDEN_NON_ZERO_ROTATION,
58             HIDDEN_NO_TASKS,
59             HIDDEN_NO_RECENTS,
60             HIDDEN_SPLIT_SCREEN,
61             HIDDEN_SPLIT_SELECT_ACTIVE,
62             HIDDEN_ACTIONS_IN_MENU,
63             HIDDEN_DESKTOP
64     })
65     @Retention(RetentionPolicy.SOURCE)
66     public @interface ActionsHiddenFlags { }
67 
68     public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 0;
69     public static final int HIDDEN_NO_TASKS = 1 << 1;
70     public static final int HIDDEN_NO_RECENTS = 1 << 2;
71     public static final int HIDDEN_SPLIT_SCREEN = 1 << 3;
72     public static final int HIDDEN_SPLIT_SELECT_ACTIVE = 1 << 4;
73     public static final int HIDDEN_ACTIONS_IN_MENU = 1 << 5;
74     public static final int HIDDEN_DESKTOP = 1 << 6;
75 
76     @IntDef(flag = true, value = {
77             DISABLED_SCROLLING,
78             DISABLED_ROTATED,
79             DISABLED_NO_THUMBNAIL})
80     @Retention(RetentionPolicy.SOURCE)
81     public @interface ActionsDisabledFlags { }
82 
83     public static final int DISABLED_SCROLLING = 1 << 0;
84     public static final int DISABLED_ROTATED = 1 << 1;
85     public static final int DISABLED_NO_THUMBNAIL = 1 << 2;
86 
87     private static final int INDEX_CONTENT_ALPHA = 0;
88     private static final int INDEX_VISIBILITY_ALPHA = 1;
89     private static final int INDEX_FULLSCREEN_ALPHA = 2;
90     private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3;
91     private static final int INDEX_SHARE_TARGET_ALPHA = 4;
92     private static final int INDEX_SCROLL_ALPHA = 5;
93     private static final int INDEX_GROUPED_ALPHA = 6;
94     private static final int INDEX_3P_LAUNCHER = 7;
95     private static final int NUM_ALPHAS = 8;
96 
97     public @interface SplitButtonHiddenFlags { }
98     public static final int FLAG_SMALL_SCREEN_HIDE_SPLIT = 1 << 0;
99 
100     /**
101      * Holds an AnimatedFloat for each alpha property, used to set or animate alpha values in
102      * {@link #mMultiValueAlphas}.
103      */
104     private final AnimatedFloat[] mAlphaProperties = new AnimatedFloat[NUM_ALPHAS];
105 
106     /** Holds MultiValueAlpha values for all actions bars */
107     private final MultiValueAlpha[] mMultiValueAlphas = new MultiValueAlpha[2];
108     /** Index used for single-task actions in the mMultiValueAlphas array */
109     private static final int ACTIONS_ALPHAS = 0;
110     /** Index used for grouped-task actions in the mMultiValueAlphas array */
111     private static final int GROUP_ACTIONS_ALPHAS = 1;
112 
113     /** Container for the action buttons below a focused, non-split Overview tile. */
114     protected LinearLayout mActionButtons;
115     private Button mSplitButton;
116     /**
117      * The "save app pair" button. Currently this is the only button that is not contained in
118      * mActionButtons, since it is the sole button that appears for a grouped task.
119      */
120     private Button mSaveAppPairButton;
121 
122     @ActionsHiddenFlags
123     private int mHiddenFlags;
124 
125     @ActionsDisabledFlags
126     protected int mDisabledFlags;
127 
128     @SplitButtonHiddenFlags
129     private int mSplitButtonHiddenFlags;
130 
131     @Nullable
132     protected T mCallbacks;
133 
134     @Nullable
135     protected DeviceProfile mDp;
136     private final Rect mTaskSize = new Rect();
137     private boolean mIsGroupedTask = false;
138     private boolean mCanSaveAppPair = false;
139 
OverviewActionsView(Context context)140     public OverviewActionsView(Context context) {
141         this(context, null);
142     }
143 
OverviewActionsView(Context context, @Nullable AttributeSet attrs)144     public OverviewActionsView(Context context, @Nullable AttributeSet attrs) {
145         this(context, attrs, 0);
146     }
147 
OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)148     public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
149         super(context, attrs, defStyleAttr, 0);
150     }
151 
152     @Override
onFinishInflate()153     protected void onFinishInflate() {
154         super.onFinishInflate();
155         // Initialize 2 view containers: one for single tasks, one for grouped tasks.
156         // These will take up the same space on the screen and alternate visibility as needed.
157         // Currently, the only grouped task action is "save app pairs".
158         mActionButtons = findViewById(R.id.action_buttons);
159         mSaveAppPairButton = findViewById(R.id.action_save_app_pair);
160         // Initialize a list to hold alphas for mActionButtons and any group action buttons.
161         mMultiValueAlphas[ACTIONS_ALPHAS] = new MultiValueAlpha(mActionButtons, NUM_ALPHAS);
162         mMultiValueAlphas[GROUP_ACTIONS_ALPHAS] =
163                 new MultiValueAlpha(mSaveAppPairButton, NUM_ALPHAS);
164         Arrays.stream(mMultiValueAlphas).forEach(a -> a.setUpdateVisibility(true));
165         // To control alpha simultaneously on mActionButtons and any group action buttons, we set up
166         // an AnimatedFloat for each alpha property.
167         for (int i = 0; i < NUM_ALPHAS; i++) {
168             final int index = i;
169             mAlphaProperties[index] = new AnimatedFloat(() -> {
170                 for (MultiValueAlpha multiValueAlpha : mMultiValueAlphas) {
171                     multiValueAlpha.get(index).setValue(mAlphaProperties[index].value);
172                 }
173             }, 1f /* initialValue */);
174         }
175 
176         // The screenshot button is implemented as a Button in launcher3 and NexusLauncher, but is
177         // an ImageButton in go launcher (does not share a common class with Button). Take care when
178         // casting this.
179         View screenshotButton = findViewById(R.id.action_screenshot);
180         screenshotButton.setOnClickListener(this);
181         mSplitButton = findViewById(R.id.action_split);
182         mSplitButton.setOnClickListener(this);
183         mSaveAppPairButton.setOnClickListener(this);
184     }
185 
186     /**
187      * Set listener for callbacks on action button taps.
188      *
189      * @param callbacks for callbacks, or {@code null} to clear the listener.
190      */
setCallbacks(T callbacks)191     public void setCallbacks(T callbacks) {
192         mCallbacks = callbacks;
193     }
194 
195     @Override
onClick(View view)196     public void onClick(View view) {
197         if (mCallbacks == null) {
198             return;
199         }
200         int id = view.getId();
201         if (id == R.id.action_screenshot) {
202             mCallbacks.onScreenshot();
203         } else if (id == R.id.action_split) {
204             mCallbacks.onSplit();
205         } else if (id == R.id.action_save_app_pair) {
206             mCallbacks.onSaveAppPair();
207         }
208     }
209 
210     @Override
onConfigurationChanged(Configuration newConfig)211     protected void onConfigurationChanged(Configuration newConfig) {
212         super.onConfigurationChanged(newConfig);
213         updateVerticalMargin(DisplayController.getNavigationMode(getContext()));
214     }
215 
216     @Override
setInsets(Rect insets)217     public void setInsets(Rect insets) {
218         mInsets.set(insets);
219         updateVerticalMargin(DisplayController.getNavigationMode(getContext()));
220         updatePadding();
221     }
222 
updateHiddenFlags(@ctionsHiddenFlags int visibilityFlags, boolean enable)223     public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
224         if (enable) {
225             mHiddenFlags |= visibilityFlags;
226         } else {
227             mHiddenFlags &= ~visibilityFlags;
228         }
229         boolean isHidden = mHiddenFlags != 0;
230         mAlphaProperties[INDEX_HIDDEN_FLAGS_ALPHA].updateValue(isHidden ? 0 : 1);
231     }
232 
233     /**
234      * Updates the proper disabled flag to indicate whether OverviewActionsView should be enabled.
235      * Ignores DISABLED_ROTATED flag for determining enabled. Flag is used to enable/disable
236      * buttons individually, currently done for select button in subclass.
237      *
238      * @param disabledFlags The flag to update.
239      * @param enable        Whether to enable the disable flag: True will cause view to be disabled.
240      */
updateDisabledFlags(@ctionsDisabledFlags int disabledFlags, boolean enable)241     public void updateDisabledFlags(@ActionsDisabledFlags int disabledFlags, boolean enable) {
242         if (enable) {
243             mDisabledFlags |= disabledFlags;
244         } else {
245             mDisabledFlags &= ~disabledFlags;
246         }
247         boolean isEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
248         LayoutUtils.setViewEnabled(this, isEnabled);
249     }
250 
251     /**
252      * Updates a batch of flags to hide and show actions buttons when a grouped task (split screen)
253      * is focused.
254      * @param isGroupedTask True if the focused task is a grouped task.
255      * @param canSaveAppPair True if the focused task is a grouped task and can be saved as an app
256      *                      pair.
257      */
updateForGroupedTask(boolean isGroupedTask, boolean canSaveAppPair)258     public void updateForGroupedTask(boolean isGroupedTask, boolean canSaveAppPair) {
259         Log.d(TAG, "updateForGroupedTask() called with: isGroupedTask = [" + isGroupedTask
260                 + "], canSaveAppPair = [" + canSaveAppPair + "]");
261         mIsGroupedTask = isGroupedTask;
262         mCanSaveAppPair = canSaveAppPair;
263         updateActionButtonsVisibility();
264     }
265 
266     /**
267      * Updates a batch of flags to hide and show actions buttons for tablet/non tablet case.
268      */
updateForIsTablet()269     private void updateForIsTablet() {
270         assert mDp != null;
271         // Update flags to see if split button should be hidden.
272         updateSplitButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_SPLIT, !mDp.isTablet);
273         updateActionButtonsVisibility();
274     }
275 
updateActionButtonsVisibility()276     private void updateActionButtonsVisibility() {
277         assert mDp != null;
278         boolean showSingleTaskActions = !mIsGroupedTask;
279         boolean showGroupActions = mIsGroupedTask && mDp.isTablet && mCanSaveAppPair;
280         Log.d(TAG, "updateActionButtonsVisibility() called: showSingleTaskActions = ["
281                 + showSingleTaskActions + "], showGroupActions = [" + showGroupActions + "]");
282         getActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showSingleTaskActions ? 1 : 0);
283         getGroupActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showGroupActions ? 1 : 0);
284     }
285 
286     /**
287      * Updates flags to hide and show actions buttons for 1p/3p launchers.
288      */
updateFor3pLauncher(boolean is3pLauncher)289     public void updateFor3pLauncher(boolean is3pLauncher) {
290         getGroupActionsAlphas().get(INDEX_3P_LAUNCHER).setValue(is3pLauncher ? 0 : 1);
291     }
292 
getActionsAlphas()293     private MultiValueAlpha getActionsAlphas() {
294         return mMultiValueAlphas[ACTIONS_ALPHAS];
295     }
296 
getGroupActionsAlphas()297     private MultiValueAlpha getGroupActionsAlphas() {
298         return mMultiValueAlphas[GROUP_ACTIONS_ALPHAS];
299     }
300 
301     /**
302      * Updates the proper flags to indicate whether the "Split screen" button should be hidden.
303      *
304      * @param flag   The flag to update.
305      * @param enable Whether to enable the hidden flag: True will cause view to be hidden.
306      */
updateSplitButtonHiddenFlags(@plitButtonHiddenFlags int flag, boolean enable)307     void updateSplitButtonHiddenFlags(@SplitButtonHiddenFlags int flag,
308             boolean enable) {
309         if (mSplitButton == null) return;
310         if (enable) {
311             mSplitButtonHiddenFlags |= flag;
312         } else {
313             mSplitButtonHiddenFlags &= ~flag;
314         }
315         int desiredVisibility = mSplitButtonHiddenFlags == 0 ? VISIBLE : GONE;
316         if (mSplitButton.getVisibility() != desiredVisibility) {
317             mSplitButton.setVisibility(desiredVisibility);
318             mActionButtons.requestLayout();
319         }
320     }
321 
getContentAlpha()322     public AnimatedFloat getContentAlpha() {
323         return mAlphaProperties[INDEX_CONTENT_ALPHA];
324     }
325 
getVisibilityAlpha()326     public AnimatedFloat getVisibilityAlpha() {
327         return mAlphaProperties[INDEX_VISIBILITY_ALPHA];
328     }
329 
getFullscreenAlpha()330     public AnimatedFloat getFullscreenAlpha() {
331         return mAlphaProperties[INDEX_FULLSCREEN_ALPHA];
332     }
333 
getShareTargetAlpha()334     public AnimatedFloat getShareTargetAlpha() {
335         return mAlphaProperties[INDEX_SHARE_TARGET_ALPHA];
336     }
337 
getIndexScrollAlpha()338     public AnimatedFloat getIndexScrollAlpha() {
339         return mAlphaProperties[INDEX_SCROLL_ALPHA];
340     }
341 
342     /**
343      * Returns the visibility of the overview actions buttons.
344      */
areActionsButtonsVisible()345     public boolean areActionsButtonsVisible() {
346         return mActionButtons.getVisibility() == View.VISIBLE
347                 || mSaveAppPairButton.getVisibility() == View.VISIBLE;
348     }
349 
350     /**
351      * Offsets OverviewActionsView horizontal position based on 3 button nav container in taskbar.
352      */
updatePadding()353     private void updatePadding() {
354         // If taskbar is in overview, overview action has dedicated space above nav buttons
355         setPadding(mInsets.left, 0, mInsets.right, 0);
356     }
357 
358     /** Updates vertical margins for different navigation mode or configuration changes. */
updateVerticalMargin(NavigationMode mode)359     public void updateVerticalMargin(NavigationMode mode) {
360         updateActionBarPosition(mActionButtons);
361         updateActionBarPosition(mSaveAppPairButton);
362     }
363 
364     /** Positions actions buttons according to device settings and insets. */
updateActionBarPosition(View actionBar)365     private void updateActionBarPosition(View actionBar) {
366         if (mDp == null) {
367             return;
368         }
369 
370         LayoutParams actionParams = (LayoutParams) actionBar.getLayoutParams();
371         actionParams.setMargins(
372                 actionParams.leftMargin, mDp.overviewActionsTopMarginPx,
373                 actionParams.rightMargin, getBottomMargin());
374     }
375 
getBottomMargin()376     private int getBottomMargin() {
377         if (mDp == null) {
378             return 0;
379         }
380 
381         if (mDp.isTablet && Flags.enableGridOnlyOverview()) {
382             return mDp.stashedTaskbarHeight;
383         }
384 
385         // Align to bottom of task Rect.
386         return mDp.heightPx - mTaskSize.bottom - mDp.overviewActionsTopMarginPx
387                 - mDp.overviewActionsHeight;
388     }
389 
390     /**
391      * Updates device profile and task size for this view to draw with.
392      */
updateDimension(DeviceProfile dp, Rect taskSize)393     public void updateDimension(DeviceProfile dp, Rect taskSize) {
394         mDp = dp;
395         mTaskSize.set(taskSize);
396         updateVerticalMargin(DisplayController.getNavigationMode(getContext()));
397         updateForIsTablet();
398 
399         requestLayout();
400 
401         int splitIconRes = dp.isLeftRightSplit
402                 ? R.drawable.ic_split_horizontal
403                 : R.drawable.ic_split_vertical;
404         mSplitButton.setCompoundDrawablesRelativeWithIntrinsicBounds(splitIconRes, 0, 0, 0);
405 
406         int appPairIconRes = dp.isLeftRightSplit
407                 ? R.drawable.ic_save_app_pair_left_right
408                 : R.drawable.ic_save_app_pair_up_down;
409         mSaveAppPairButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
410                 appPairIconRes, 0, 0, 0);
411     }
412 }
413