1 /*
2  * Copyright (C) 2019 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.systemui.globalactions;
18 
19 import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
20 import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
21 import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
22 
23 import android.content.Context;
24 import android.util.AttributeSet;
25 import android.view.Gravity;
26 import android.view.View;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.systemui.R;
30 
31 /**
32  * Grid-based implementation of the button layout created by the global actions dialog.
33  */
34 public class GlobalActionsColumnLayout extends GlobalActionsLayout {
35     private boolean mLastSnap;
36 
GlobalActionsColumnLayout(Context context, AttributeSet attrs)37     public GlobalActionsColumnLayout(Context context, AttributeSet attrs) {
38         super(context, attrs);
39     }
40 
41     @Override
onLayout(boolean changed, int l, int t, int r, int b)42     protected void onLayout(boolean changed, int l, int t, int r, int b) {
43         super.onLayout(changed, l, t, r, b);
44 
45         post(() -> updateSnap());
46     }
47 
48     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)49     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
50         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
51     }
52 
53     @VisibleForTesting
shouldReverseListItems()54     protected boolean shouldReverseListItems() {
55         int rotation = getCurrentRotation();
56         if (rotation == ROTATION_NONE) {
57             return false;
58         }
59         if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
60             return rotation == ROTATION_LANDSCAPE;
61         }
62         return rotation == ROTATION_SEASCAPE;
63     }
64 
65     @Override
onUpdateList()66     public void onUpdateList() {
67         super.onUpdateList();
68         updateChildOrdering();
69     }
70 
updateChildOrdering()71     private void updateChildOrdering() {
72         if (shouldReverseListItems()) {
73             getListView().bringToFront();
74         } else {
75             getSeparatedView().bringToFront();
76         }
77     }
78 
79     /**
80      *  Snap this layout to align with the power button.
81      */
82     @VisibleForTesting
snapToPowerButton()83     protected void snapToPowerButton() {
84         int offset = getPowerButtonOffsetDistance();
85         switch (getCurrentRotation()) {
86             case (ROTATION_LANDSCAPE):
87                 setPadding(offset, 0, 0, 0);
88                 setGravity(Gravity.LEFT | Gravity.TOP);
89                 break;
90             case (ROTATION_SEASCAPE):
91                 setPadding(0, 0, offset, 0);
92                 setGravity(Gravity.RIGHT | Gravity.BOTTOM);
93                 break;
94             default:
95                 setPadding(0, offset, 0, 0);
96                 setGravity(Gravity.TOP | Gravity.RIGHT);
97                 break;
98         }
99     }
100 
101     /**
102      *  Detach this layout from snapping to the power button and instead center along that edge.
103      */
104     @VisibleForTesting
centerAlongEdge()105     protected void centerAlongEdge() {
106         switch (getCurrentRotation()) {
107             case (ROTATION_LANDSCAPE):
108                 setPadding(0, 0, 0, 0);
109                 setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP);
110                 break;
111             case (ROTATION_SEASCAPE):
112                 setPadding(0, 0, 0, 0);
113                 setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
114                 break;
115             default:
116                 setPadding(0, 0, 0, 0);
117                 setGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
118                 break;
119         }
120     }
121 
122     /**
123      * Determines the distance from the top of the screen to the power button.
124      */
125     @VisibleForTesting
getPowerButtonOffsetDistance()126     protected int getPowerButtonOffsetDistance() {
127         return Math.round(getContext().getResources().getDimension(
128                 R.dimen.global_actions_top_padding));
129     }
130 
131     /**
132      * Check whether there is enough extra space below the dialog such that we can offset the top
133      * of the dialog from the top of the phone to line it up with the power button, then either
134      * snap the dialog to the power button or center it along the edge with snapToPowerButton.
135      */
136     @VisibleForTesting
shouldSnapToPowerButton()137     protected boolean shouldSnapToPowerButton() {
138         int offsetSize = getPowerButtonOffsetDistance();
139         int dialogSize;
140         int screenSize;
141         View wrapper = getWrapper();
142         int rotation = getCurrentRotation();
143         if (rotation == ROTATION_NONE) {
144             dialogSize = wrapper.getMeasuredHeight();
145             screenSize = getMeasuredHeight();
146         } else {
147             dialogSize = wrapper.getMeasuredWidth();
148             screenSize = getMeasuredWidth();
149         }
150         return dialogSize + offsetSize < screenSize;
151     }
152 
153     @VisibleForTesting
updateSnap()154     protected void updateSnap() {
155         boolean snap = shouldSnapToPowerButton();
156         if (snap != mLastSnap) {
157             if (snap) {
158                 snapToPowerButton();
159             } else {
160                 centerAlongEdge();
161             }
162         }
163         mLastSnap = snap;
164     }
165 
166     @VisibleForTesting
getGridItemSize()167     protected float getGridItemSize() {
168         return getContext().getResources().getDimension(R.dimen.global_actions_grid_item_height);
169     }
170 
171     @VisibleForTesting
getAnimationDistance()172     protected float getAnimationDistance() {
173         return getGridItemSize() / 2;
174     }
175 
176     @Override
getAnimationOffsetX()177     public float getAnimationOffsetX() {
178         if (getCurrentRotation() == ROTATION_NONE) {
179             return getAnimationDistance();
180         }
181         return 0;
182     }
183 
184     @Override
getAnimationOffsetY()185     public float getAnimationOffsetY() {
186         switch (getCurrentRotation()) {
187             case ROTATION_LANDSCAPE:
188                 return -getAnimationDistance();
189             case ROTATION_SEASCAPE:
190                 return getAnimationDistance();
191             default: // Portrait
192                 return 0;
193         }
194     }
195 }
196