1/*
2 * Copyright (C) 2014 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.qs;
18
19import android.content.Context;
20import android.graphics.Typeface;
21import android.graphics.drawable.Drawable;
22import android.text.TextUtils;
23import android.text.TextUtils.TruncateAt;
24import android.view.Gravity;
25import android.view.View;
26import android.widget.ImageView;
27import android.widget.LinearLayout;
28import android.widget.TextView;
29
30import com.android.systemui.R;
31
32import java.util.Objects;
33
34/**
35 * Text displayed over one or two lines, centered horizontally.  A caret is always drawn at the end
36 * of the first line, and considered part of the content for centering purposes.
37 *
38 * Text overflow rules:
39 *   First line: break on a word, unless a single word takes up the entire line - in which case
40 *               truncate.
41 *   Second line: ellipsis if necessary
42 */
43public class QSDualTileLabel extends LinearLayout {
44
45    private final Context mContext;
46    private final TextView mFirstLine;
47    private final ImageView mFirstLineCaret;
48    private final TextView mSecondLine;
49    private final int mHorizontalPaddingPx;
50
51    private String mText;
52
53    public QSDualTileLabel(Context context) {
54        super(context);
55        mContext = context;
56        setOrientation(LinearLayout.VERTICAL);
57
58        mHorizontalPaddingPx = mContext.getResources()
59                .getDimensionPixelSize(R.dimen.qs_dual_tile_padding_horizontal);
60
61        mFirstLine = initTextView();
62        mFirstLine.setPadding(mHorizontalPaddingPx, 0, mHorizontalPaddingPx, 0);
63        final LinearLayout firstLineLayout = new LinearLayout(mContext);
64        firstLineLayout.setPadding(0, 0, 0, 0);
65        firstLineLayout.setOrientation(LinearLayout.HORIZONTAL);
66        firstLineLayout.setClickable(false);
67        firstLineLayout.setBackground(null);
68        firstLineLayout.addView(mFirstLine);
69        mFirstLineCaret = new ImageView(mContext);
70        mFirstLineCaret.setScaleType(ImageView.ScaleType.MATRIX);
71        mFirstLineCaret.setClickable(false);
72        firstLineLayout.addView(mFirstLineCaret);
73        addView(firstLineLayout, newLinearLayoutParams());
74
75        mSecondLine = initTextView();
76        mSecondLine.setPadding(mHorizontalPaddingPx, 0, mHorizontalPaddingPx, 0);
77        mSecondLine.setEllipsize(TruncateAt.END);
78        mSecondLine.setVisibility(GONE);
79        addView(mSecondLine, newLinearLayoutParams());
80
81        addOnLayoutChangeListener(new OnLayoutChangeListener() {
82            @Override
83            public void onLayoutChange(View v, int left, int top, int right,
84                    int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
85                if ((oldRight - oldLeft) != (right - left)) {
86                    rescheduleUpdateText();
87                }
88            }
89        });
90    }
91
92    private static LayoutParams newLinearLayoutParams() {
93        final LayoutParams lp =
94                new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
95        lp.gravity = Gravity.CENTER_HORIZONTAL;
96        return lp;
97    }
98
99    public void setFirstLineCaret(Drawable d) {
100        mFirstLineCaret.setImageDrawable(d);
101        if (d != null) {
102            final int h = d.getIntrinsicHeight();
103            mFirstLine.setMinHeight(h);
104            mFirstLine.setPadding(mHorizontalPaddingPx, 0, 0, 0);
105        }
106    }
107
108    private TextView initTextView() {
109        final TextView tv = new TextView(mContext);
110        tv.setPadding(0, 0, 0, 0);
111        tv.setGravity(Gravity.CENTER_VERTICAL);
112        tv.setSingleLine(true);
113        tv.setClickable(false);
114        tv.setBackground(null);
115        return tv;
116    }
117
118    public void setText(CharSequence text) {
119        final String newText = text == null ? null : text.toString().trim();
120        if (Objects.equals(newText, mText)) return;
121        mText = newText;
122        rescheduleUpdateText();
123    }
124
125    public String getText() {
126        return mText;
127    }
128
129    public void setTextSize(int unit, float size) {
130        mFirstLine.setTextSize(unit, size);
131        mSecondLine.setTextSize(unit, size);
132        rescheduleUpdateText();
133    }
134
135    public void setTextColor(int color) {
136        mFirstLine.setTextColor(color);
137        mSecondLine.setTextColor(color);
138        rescheduleUpdateText();
139    }
140
141    public void setTypeface(Typeface tf) {
142        mFirstLine.setTypeface(tf);
143        mSecondLine.setTypeface(tf);
144        rescheduleUpdateText();
145    }
146
147    private void rescheduleUpdateText() {
148        removeCallbacks(mUpdateText);
149        post(mUpdateText);
150    }
151
152    private void updateText() {
153        if (getWidth() == 0) return;
154        if (TextUtils.isEmpty(mText)) {
155            mFirstLine.setText(null);
156            mSecondLine.setText(null);
157            mSecondLine.setVisibility(GONE);
158            return;
159        }
160        final float maxWidth = getWidth() - mFirstLineCaret.getWidth() - mHorizontalPaddingPx
161                - getPaddingLeft() - getPaddingRight();
162        float width = mFirstLine.getPaint().measureText(mText);
163        if (width <= maxWidth) {
164            mFirstLine.setText(mText);
165            mSecondLine.setText(null);
166            mSecondLine.setVisibility(GONE);
167            return;
168        }
169        final int n = mText.length();
170        int lastWordBoundary = -1;
171        boolean inWhitespace = false;
172        int i = 0;
173        for (i = 1; i < n; i++) {
174            width = mFirstLine.getPaint().measureText(mText.substring(0, i));
175            final boolean done = width > maxWidth;
176            if (Character.isWhitespace(mText.charAt(i))) {
177                if (!inWhitespace && !done) {
178                    lastWordBoundary = i;
179                }
180                inWhitespace = true;
181            } else {
182                inWhitespace = false;
183            }
184            if (done) {
185                break;
186            }
187        }
188        if (lastWordBoundary == -1) {
189            lastWordBoundary = i - 1;
190        }
191        mFirstLine.setText(mText.substring(0, lastWordBoundary));
192        mSecondLine.setText(mText.substring(lastWordBoundary).trim());
193        mSecondLine.setVisibility(VISIBLE);
194    }
195
196    private final Runnable mUpdateText = new Runnable() {
197        @Override
198        public void run() {
199            updateText();
200        }
201    };
202}
203