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.view.View;
24 import android.view.View.OnClickListener;
25 import android.view.ViewGroup;
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.Insettable;
35 import com.android.launcher3.R;
36 import com.android.launcher3.util.MultiValueAlpha;
37 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
38 import com.android.quickstep.SysUINavigationMode;
39 import com.android.quickstep.SysUINavigationMode.Mode;
40 import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
41 import com.android.quickstep.util.LayoutUtils;
42 
43 import java.lang.annotation.Retention;
44 import java.lang.annotation.RetentionPolicy;
45 
46 /**
47  * View for showing action buttons in Overview
48  */
49 public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayout
50         implements OnClickListener, Insettable {
51 
52     private final Rect mInsets = new Rect();
53 
54     @IntDef(flag = true, value = {
55             HIDDEN_NON_ZERO_ROTATION,
56             HIDDEN_NO_TASKS,
57             HIDDEN_NO_RECENTS,
58             HIDDEN_FOCUSED_SCROLL,
59             HIDDEN_SPLIT_SCREEN})
60     @Retention(RetentionPolicy.SOURCE)
61     public @interface ActionsHiddenFlags { }
62 
63     public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 0;
64     public static final int HIDDEN_NO_TASKS = 1 << 1;
65     public static final int HIDDEN_NO_RECENTS = 1 << 2;
66     public static final int HIDDEN_FOCUSED_SCROLL = 1 << 3;
67     public static final int HIDDEN_SPLIT_SCREEN = 1 << 4;
68 
69     @IntDef(flag = true, value = {
70             DISABLED_SCROLLING,
71             DISABLED_ROTATED,
72             DISABLED_NO_THUMBNAIL})
73     @Retention(RetentionPolicy.SOURCE)
74     public @interface ActionsDisabledFlags { }
75 
76     public static final int DISABLED_SCROLLING = 1 << 0;
77     public static final int DISABLED_ROTATED = 1 << 1;
78     public static final int DISABLED_NO_THUMBNAIL = 1 << 2;
79 
80     private static final int INDEX_CONTENT_ALPHA = 0;
81     private static final int INDEX_VISIBILITY_ALPHA = 1;
82     private static final int INDEX_FULLSCREEN_ALPHA = 2;
83     private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3;
84 
85     private final MultiValueAlpha mMultiValueAlpha;
86     private Button mSplitButton;
87 
88     @ActionsHiddenFlags
89     private int mHiddenFlags;
90 
91     @ActionsDisabledFlags
92     protected int mDisabledFlags;
93 
94     @Nullable
95     protected T mCallbacks;
96 
97     @Nullable
98     protected DeviceProfile mDp;
99 
OverviewActionsView(Context context)100     public OverviewActionsView(Context context) {
101         this(context, null);
102     }
103 
OverviewActionsView(Context context, @Nullable AttributeSet attrs)104     public OverviewActionsView(Context context, @Nullable AttributeSet attrs) {
105         this(context, attrs, 0);
106     }
107 
OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)108     public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
109         super(context, attrs, defStyleAttr, 0);
110         mMultiValueAlpha = new MultiValueAlpha(this, 5);
111         mMultiValueAlpha.setUpdateVisibility(true);
112     }
113 
114     @Override
onFinishInflate()115     protected void onFinishInflate() {
116         super.onFinishInflate();
117         findViewById(R.id.action_screenshot).setOnClickListener(this);
118 
119         mSplitButton = findViewById(R.id.action_split);
120         mSplitButton.setOnClickListener(this);
121     }
122 
123     /**
124      * Set listener for callbacks on action button taps.
125      *
126      * @param callbacks for callbacks, or {@code null} to clear the listener.
127      */
setCallbacks(T callbacks)128     public void setCallbacks(T callbacks) {
129         mCallbacks = callbacks;
130     }
131 
132     @Override
onClick(View view)133     public void onClick(View view) {
134         if (mCallbacks == null) {
135             return;
136         }
137         int id = view.getId();
138         if (id == R.id.action_screenshot) {
139             mCallbacks.onScreenshot();
140         } else if (id == R.id.action_split) {
141             mCallbacks.onSplit();
142         }
143     }
144 
145     @Override
onConfigurationChanged(Configuration newConfig)146     protected void onConfigurationChanged(Configuration newConfig) {
147         super.onConfigurationChanged(newConfig);
148         updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
149     }
150 
151     @Override
setInsets(Rect insets)152     public void setInsets(Rect insets) {
153         mInsets.set(insets);
154         updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
155         updateHorizontalPadding();
156     }
157 
updateHiddenFlags(@ctionsHiddenFlags int visibilityFlags, boolean enable)158     public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
159         if (enable) {
160             mHiddenFlags |= visibilityFlags;
161         } else {
162             mHiddenFlags &= ~visibilityFlags;
163         }
164         boolean isHidden = mHiddenFlags != 0;
165         mMultiValueAlpha.getProperty(INDEX_HIDDEN_FLAGS_ALPHA).setValue(isHidden ? 0 : 1);
166     }
167 
168     /**
169      * Updates the proper disabled flag to indicate whether OverviewActionsView should be enabled.
170      * Ignores DISABLED_ROTATED flag for determining enabled. Flag is used to enable/disable
171      * buttons individually, currently done for select button in subclass.
172      *
173      * @param disabledFlags The flag to update.
174      * @param enable        Whether to enable the disable flag: True will cause view to be disabled.
175      */
updateDisabledFlags(@ctionsDisabledFlags int disabledFlags, boolean enable)176     public void updateDisabledFlags(@ActionsDisabledFlags int disabledFlags, boolean enable) {
177         if (enable) {
178             mDisabledFlags |= disabledFlags;
179         } else {
180             mDisabledFlags &= ~disabledFlags;
181         }
182         boolean isEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
183         LayoutUtils.setViewEnabled(this, isEnabled);
184     }
185 
getContentAlpha()186     public AlphaProperty getContentAlpha() {
187         return mMultiValueAlpha.getProperty(INDEX_CONTENT_ALPHA);
188     }
189 
getVisibilityAlpha()190     public AlphaProperty getVisibilityAlpha() {
191         return mMultiValueAlpha.getProperty(INDEX_VISIBILITY_ALPHA);
192     }
193 
getFullscreenAlpha()194     public AlphaProperty getFullscreenAlpha() {
195         return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA);
196     }
197 
updateHorizontalPadding()198     private void updateHorizontalPadding() {
199         setPadding(mInsets.left, 0, mInsets.right, 0);
200     }
201 
202     /** Updates vertical margins for different navigation mode or configuration changes. */
updateVerticalMargin(Mode mode)203     public void updateVerticalMargin(Mode mode) {
204         if (mDp == null) {
205             return;
206         }
207         LayoutParams actionParams = (LayoutParams) findViewById(
208                 R.id.action_buttons).getLayoutParams();
209         actionParams.setMargins(
210                 actionParams.leftMargin, getOverviewActionsTopMarginPx(mode, mDp),
211                 actionParams.rightMargin, getOverviewActionsBottomMarginPx(mode, mDp));
212     }
213 
214     /**
215      * Set the device profile for this view to draw with.
216      */
setDp(DeviceProfile dp)217     public void setDp(DeviceProfile dp) {
218         mDp = dp;
219         updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
220 
221         LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
222                 dp.isVerticalBarLayout() ? 0 : dp.overviewActionsButtonSpacing,
223                 ViewGroup.LayoutParams.MATCH_PARENT);
224         params.weight = dp.isVerticalBarLayout() ? 1 : 0;
225         findViewById(R.id.action_split_space).setLayoutParams(params);
226 
227         requestLayout();
228 
229         mSplitButton.setCompoundDrawablesWithIntrinsicBounds(
230                 (dp.isLandscape ? R.drawable.ic_split_horizontal : R.drawable.ic_split_vertical),
231                 0, 0, 0);
232     }
233 
setSplitButtonVisible(boolean visible)234     public void setSplitButtonVisible(boolean visible) {
235         if (mSplitButton == null) {
236             return;
237         }
238 
239         mSplitButton.setVisibility(visible ? VISIBLE : GONE);
240         findViewById(R.id.action_split_space).setVisibility(visible ? VISIBLE : GONE);
241     }
242 
243     /** Get the top margin associated with the action buttons in Overview. */
getOverviewActionsTopMarginPx( SysUINavigationMode.Mode mode, DeviceProfile dp)244     public static int getOverviewActionsTopMarginPx(
245             SysUINavigationMode.Mode mode, DeviceProfile dp) {
246         // In vertical bar, use the smaller task margin for the top regardless of mode
247         if (dp.isVerticalBarLayout()) {
248             return dp.overviewTaskMarginPx;
249         }
250 
251         if (mode == SysUINavigationMode.Mode.THREE_BUTTONS) {
252             return dp.overviewActionsMarginThreeButtonPx;
253         }
254 
255         return dp.overviewActionsTopMarginGesturePx;
256     }
257 
258     /** Get the bottom margin associated with the action buttons in Overview. */
getOverviewActionsBottomMarginPx( SysUINavigationMode.Mode mode, DeviceProfile dp)259     public static int getOverviewActionsBottomMarginPx(
260             SysUINavigationMode.Mode mode, DeviceProfile dp) {
261         int inset = dp.getInsets().bottom;
262 
263         if (dp.isVerticalBarLayout()) {
264             return inset;
265         }
266 
267         if (mode == SysUINavigationMode.Mode.THREE_BUTTONS) {
268             return dp.overviewActionsMarginThreeButtonPx + inset;
269         }
270 
271         return dp.overviewActionsBottomMarginGesturePx + inset;
272     }
273 }
274