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
17package com.android.systemui.statusbar;
18
19import android.content.Context;
20import android.util.AttributeSet;
21import android.util.DisplayMetrics;
22import android.util.TypedValue;
23import android.view.View;
24import 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 */
30public final class KeyboardShortcutKeysLayout extends ViewGroup {
31    private int mLineHeight;
32    private final Context mContext;
33
34    public KeyboardShortcutKeysLayout(Context context) {
35        super(context);
36        this.mContext = context;
37    }
38
39    public KeyboardShortcutKeysLayout(Context context, AttributeSet attrs) {
40        super(context, attrs);
41        this.mContext = context;
42    }
43
44    @Override
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
90    protected LayoutParams generateDefaultLayoutParams() {
91        int spacing = getHorizontalVerticalSpacing();
92        return new LayoutParams(spacing, spacing);
93    }
94
95    @Override
96    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams layoutParams) {
97        int spacing = getHorizontalVerticalSpacing();
98        return new LayoutParams(spacing, spacing, layoutParams);
99    }
100
101    @Override
102    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
103        return (p instanceof LayoutParams);
104    }
105
106    @Override
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
155    private int getHorizontalVerticalSpacing() {
156        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
157        return (int) TypedValue.applyDimension(
158                TypedValue.COMPLEX_UNIT_DIP, 4, displayMetrics);
159    }
160
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