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 
17 package com.android.systemui.qs;
18 
19 import android.content.Context;
20 import android.graphics.Typeface;
21 import android.graphics.drawable.Drawable;
22 import android.text.TextUtils;
23 import android.text.TextUtils.TruncateAt;
24 import android.view.Gravity;
25 import android.view.View;
26 import android.widget.ImageView;
27 import android.widget.LinearLayout;
28 import android.widget.TextView;
29 
30 import com.android.systemui.R;
31 
32 import 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  */
43 public 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 
QSDualTileLabel(Context context)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 
newLinearLayoutParams()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 
setFirstLineCaret(Drawable d)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 
initTextView()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 
setText(CharSequence text)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 
getText()125     public String getText() {
126         return mText;
127     }
128 
setTextSize(int unit, float size)129     public void setTextSize(int unit, float size) {
130         mFirstLine.setTextSize(unit, size);
131         mSecondLine.setTextSize(unit, size);
132         rescheduleUpdateText();
133     }
134 
setTextColor(int color)135     public void setTextColor(int color) {
136         mFirstLine.setTextColor(color);
137         mSecondLine.setTextColor(color);
138         rescheduleUpdateText();
139     }
140 
setTypeface(Typeface tf)141     public void setTypeface(Typeface tf) {
142         mFirstLine.setTypeface(tf);
143         mSecondLine.setTypeface(tf);
144         rescheduleUpdateText();
145     }
146 
rescheduleUpdateText()147     private void rescheduleUpdateText() {
148         removeCallbacks(mUpdateText);
149         post(mUpdateText);
150     }
151 
updateText()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