1 /*
2  * Copyright (C) 2016 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.statusbar;
18 
19 import android.content.Context;
20 import android.util.AttributeSet;
21 import android.util.DisplayMetrics;
22 import android.util.TypedValue;
23 import android.view.View;
24 import android.view.ViewGroup;
25 
26 /**
27  * Layout used as a container for keyboard shortcut keys. It's children are wrapped and right
28  * aligned.
29  */
30 public final class KeyboardShortcutKeysLayout extends ViewGroup {
31     private int mLineHeight;
32     private final Context mContext;
33 
KeyboardShortcutKeysLayout(Context context)34     public KeyboardShortcutKeysLayout(Context context) {
35         super(context);
36         this.mContext = context;
37     }
38 
KeyboardShortcutKeysLayout(Context context, AttributeSet attrs)39     public KeyboardShortcutKeysLayout(Context context, AttributeSet attrs) {
40         super(context, attrs);
41         this.mContext = context;
42     }
43 
44     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)45     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
46         int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
47         int childCount = getChildCount();
48         int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
49         int lineHeight = 0;
50         int xPos = getPaddingLeft();
51         int yPos = getPaddingTop();
52 
53         int childHeightMeasureSpec;
54         if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
55             childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
56         } else {
57             childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
58         }
59 
60         for (int i = 0; i < childCount; i++) {
61             View child = getChildAt(i);
62             if (child.getVisibility() != GONE) {
63                 LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
64                 child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
65                         childHeightMeasureSpec);
66                 int childWidth = child.getMeasuredWidth();
67                 lineHeight = Math.max(lineHeight,
68                         child.getMeasuredHeight() + layoutParams.mVerticalSpacing);
69 
70                 if (xPos + childWidth > width) {
71                     xPos = getPaddingLeft();
72                     yPos += lineHeight;
73                 }
74                 xPos += childWidth + layoutParams.mHorizontalSpacing;
75             }
76         }
77         this.mLineHeight = lineHeight;
78 
79         if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {
80             height = yPos + lineHeight;
81         } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
82             if (yPos + lineHeight < height) {
83                 height = yPos + lineHeight;
84             }
85         }
86         setMeasuredDimension(width, height);
87     }
88 
89     @Override
generateDefaultLayoutParams()90     protected LayoutParams generateDefaultLayoutParams() {
91         int spacing = getHorizontalVerticalSpacing();
92         return new LayoutParams(spacing, spacing);
93     }
94 
95     @Override
generateLayoutParams(ViewGroup.LayoutParams layoutParams)96     protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams layoutParams) {
97         int spacing = getHorizontalVerticalSpacing();
98         return new LayoutParams(spacing, spacing, layoutParams);
99     }
100 
101     @Override
checkLayoutParams(ViewGroup.LayoutParams p)102     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
103         return (p instanceof LayoutParams);
104     }
105 
106     @Override
onLayout(boolean changed, int l, int t, int r, int b)107     protected void onLayout(boolean changed, int l, int t, int r, int b) {
108         int childCount = getChildCount();
109         int fullRowWidth = r - l;
110         int xPos = isRTL()
111                 ? fullRowWidth - getPaddingRight()
112                 : getPaddingLeft();
113         int yPos = getPaddingTop();
114         int lastHorizontalSpacing = 0;
115         // The index of the child which starts the current row.
116         int rowStartIdx = 0;
117 
118         // Go through all the children.
119         for (int i = 0; i < childCount; i++) {
120             View currentChild = getChildAt(i);
121             if (currentChild.getVisibility() != GONE) {
122                 int currentChildWidth = currentChild.getMeasuredWidth();
123                 LayoutParams lp = (LayoutParams) currentChild.getLayoutParams();
124 
125                 boolean childDoesNotFitOnRow = isRTL()
126                         ? xPos - getPaddingLeft() - currentChildWidth < 0
127                         : xPos + currentChildWidth > fullRowWidth;
128 
129                 if (childDoesNotFitOnRow) {
130                     // Layout all the children on this row but the current one.
131                     layoutChildrenOnRow(rowStartIdx, i, fullRowWidth, xPos, yPos,
132                             lastHorizontalSpacing);
133                     // Update the positions for starting on the new row.
134                     xPos = isRTL()
135                             ? fullRowWidth - getPaddingRight()
136                             : getPaddingLeft();
137                     yPos += mLineHeight;
138                     rowStartIdx = i;
139                 }
140 
141                 xPos = isRTL()
142                         ? xPos - currentChildWidth - lp.mHorizontalSpacing
143                         : xPos + currentChildWidth + lp.mHorizontalSpacing;
144                 lastHorizontalSpacing = lp.mHorizontalSpacing;
145             }
146         }
147 
148         // Lay out the children on the last row.
149         if (rowStartIdx < childCount) {
150             layoutChildrenOnRow(rowStartIdx, childCount, fullRowWidth, xPos, yPos,
151                     lastHorizontalSpacing);
152         }
153     }
154 
getHorizontalVerticalSpacing()155     private int getHorizontalVerticalSpacing() {
156         DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
157         return (int) TypedValue.applyDimension(
158                 TypedValue.COMPLEX_UNIT_DIP, 4, displayMetrics);
159     }
160 
layoutChildrenOnRow(int startIndex, int endIndex, int fullRowWidth, int xPos, int yPos, int lastHorizontalSpacing)161     private void layoutChildrenOnRow(int startIndex, int endIndex, int fullRowWidth, int xPos,
162             int yPos, int lastHorizontalSpacing) {
163         if (!isRTL()) {
164             xPos = getPaddingLeft() + fullRowWidth - xPos + lastHorizontalSpacing;
165         }
166 
167         for (int j = startIndex; j < endIndex; ++j) {
168             View currentChild = getChildAt(j);
169             int currentChildWidth = currentChild.getMeasuredWidth();
170             LayoutParams lp = (LayoutParams) currentChild.getLayoutParams();
171             if (isRTL() && j == startIndex) {
172                 xPos = fullRowWidth - xPos - getPaddingRight() - currentChildWidth
173                         - lp.mHorizontalSpacing;
174             }
175 
176             currentChild.layout(
177                     xPos,
178                     yPos,
179                     xPos + currentChildWidth,
180                     yPos + currentChild.getMeasuredHeight());
181 
182             if (isRTL()) {
183                 int nextChildWidth = j < endIndex - 1
184                         ? getChildAt(j + 1).getMeasuredWidth()
185                         : 0;
186                 xPos -= nextChildWidth + lp.mHorizontalSpacing;
187             } else {
188                 xPos += currentChildWidth + lp.mHorizontalSpacing;
189             }
190         }
191     }
192 
193     private boolean isRTL() {
194         return mContext.getResources().getConfiguration().getLayoutDirection()
195                 == View.LAYOUT_DIRECTION_RTL;
196     }
197 
198     public static class LayoutParams extends ViewGroup.LayoutParams {
199         public final int mHorizontalSpacing;
200         public final int mVerticalSpacing;
201 
202         public LayoutParams(int horizontalSpacing, int verticalSpacing,
203                 ViewGroup.LayoutParams viewGroupLayout) {
204             super(viewGroupLayout);
205             this.mHorizontalSpacing = horizontalSpacing;
206             this.mVerticalSpacing = verticalSpacing;
207         }
208 
209         public LayoutParams(int mHorizontalSpacing, int verticalSpacing) {
210             super(0, 0);
211             this.mHorizontalSpacing = mHorizontalSpacing;
212             this.mVerticalSpacing = verticalSpacing;
213         }
214     }
215 }
216