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