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