Support two line text in AllApps/OnDeviceSearch w/ feature flag

Made separate feature flag for on device search
Add unit test to test twoLine string
- Unit tests for testing newStringThatShouldSupportTwoLineText() in BubbleTextView.java. This class tests a couple of strings
and uses the getLineCount() to determine if the test passes. Verifying with getLineCount() is sufficient since BubbleTextView can only be in one line or two lines,
and this is enough to ensure whether the string should be specifically wrapped onto the second line and to ensure truncation.

bug: 201388851
test: presubmit, ran locally on big and small device, before: https://screenshot.googleplex.com/3Q6pwveFDZqxDXL (ORIGINAL TWO LINE TEXT)
after:  https://screenshot.googleplex.com/7pkwUto6HGzMYoT

Change-Id: I93e6ed179e1081d5cdffc6db9c7ae34de8021c24
This commit is contained in:
Brandon Dayauon
2023-03-09 15:13:04 -08:00
parent c523de6dc2
commit cf88ea1e62
7 changed files with 533 additions and 8 deletions
-1
View File
@@ -19,7 +19,6 @@
android:id="@+id/icon"
android:singleLine="false"
android:lines="2"
android:inputType="textMultiLine"
launcher:iconDisplay="all_apps"
launcher:centerVertically="true" />
+118 -3
View File
@@ -52,8 +52,10 @@ import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
import com.android.launcher3.dragndrop.DraggableView;
@@ -71,6 +73,8 @@ import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.search.StringMatcherUtility;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.views.ActivityContext;
@@ -97,11 +101,19 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
private static final float MIN_LETTER_SPACING = -0.05f;
private static final int MAX_SEARCH_LOOP_COUNT = 20;
private static final Character NEW_LINE = '\n';
private static final String EMPTY = "";
private static final StringMatcherUtility.StringMatcher MATCHER =
StringMatcherUtility.StringMatcher.getInstance();
private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
private float mScaleForReorderBounce = 1f;
private IntArray mBreakPointsIntArray;
private CharSequence mLastOriginalText;
private CharSequence mLastModifiedText;
private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
= new Property<BubbleTextView, Float>(Float.TYPE, "dotScale") {
@Override
@@ -134,7 +146,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
private FastBitmapDrawable mIcon;
private boolean mCenterVertically;
protected final int mDisplay;
protected int mDisplay;
private final CheckLongPressHelper mLongPressHelper;
@@ -255,6 +267,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
mDotParams.scale = 0f;
mForceHideDot = false;
setBackground(null);
setSingleLine(true);
setMaxLines(1);
setTag(null);
if (mIconLoadRequest != null) {
@@ -382,8 +396,15 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
}
@UiThread
private void applyLabel(ItemInfoWithIcon info) {
setText(info.title);
@VisibleForTesting
public void applyLabel(ItemInfoWithIcon info) {
CharSequence label = info.title;
if (label != null) {
mLastOriginalText = label;
mLastModifiedText = mLastOriginalText;
mBreakPointsIntArray = StringMatcherUtility.getListOfBreakpoints(label, MATCHER);
setText(label);
}
if (info.contentDescription != null) {
setContentDescription(info.isDisabled()
? getContext().getString(R.string.disabled_app_label, info.contentDescription)
@@ -391,6 +412,12 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
}
}
/** This is used for testing to forcefully set the display to ALL_APPS */
@VisibleForTesting
public void setDisplayAllApps() {
mDisplay = DISPLAY_ALL_APPS;
}
/**
* Overrides the default long press timeout.
*/
@@ -637,6 +664,27 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
setPadding(getPaddingLeft(), (height - cellHeightPx) / 2, getPaddingRight(),
getPaddingBottom());
}
// only apply two line for all_apps
if (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get() && (mLastOriginalText != null)
&& (mDisplay == DISPLAY_ALL_APPS)) {
CharSequence modifiedString = modifyTitleToSupportMultiLine(
MeasureSpec.getSize(widthMeasureSpec) - getCompoundPaddingLeft()
- getCompoundPaddingRight(),
mLastOriginalText,
getPaint(), mBreakPointsIntArray);
if (!TextUtils.equals(modifiedString, mLastModifiedText)) {
mLastModifiedText = modifiedString;
setText(modifiedString);
// if text contains NEW_LINE, set max lines to 2
if (TextUtils.indexOf(modifiedString, NEW_LINE) != -1) {
setSingleLine(false);
setMaxLines(2);
} else {
setSingleLine(true);
setMaxLines(1);
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@@ -697,6 +745,73 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
return ObjectAnimator.ofFloat(this, TEXT_ALPHA_PROPERTY, toAlpha);
}
/**
* Generate a new string that will support two line text depending on the current string.
* This method calculates the limited width of a text view and creates a string to fit as
* many words as it can until the limit is reached. Once the limit is reached, we decide to
* either return the original title or continue on a new line. How to get the new string is by
* iterating through the list of break points and determining if the strings between the break
* points can fit within the line it is in.
* Example assuming each character takes up one spot:
* title = "Battery Stats", breakpoint = [6], stringPtr = 0, limitedWidth = 7
* We get the current word -> from sublist(0, breakpoint[i]+1) so sublist (0,7) -> Battery,
* now stringPtr = 7 then from sublist(7) the current string is " Stats" and the runningWidth
* at this point exceeds limitedWidth and so we put " Stats" onto the next line (after checking
* if the first char is a SPACE, we trim to append "Stats". So resulting string would be
* "Battery\nStats"
*/
public static CharSequence modifyTitleToSupportMultiLine(int limitedWidth, CharSequence title,
TextPaint paint, IntArray breakPoints) {
// current title is less than the width allowed so we can just skip
if (title == null || paint.measureText(title, 0, title.length()) <= limitedWidth) {
return title;
}
float currentWordWidth, runningWidth = 0;
CharSequence currentWord;
StringBuilder newString = new StringBuilder();
int stringPtr = 0;
for (int i = 0; i < breakPoints.size()+1; i++) {
if (i < breakPoints.size()) {
currentWord = title.subSequence(stringPtr, breakPoints.get(i)+1);
} else {
// last word from recent breakpoint until the end of the string
currentWord = title.subSequence(stringPtr, title.length());
}
currentWordWidth = paint.measureText(currentWord,0, currentWord.length());
runningWidth += currentWordWidth;
if (runningWidth <= limitedWidth) {
newString.append(currentWord);
} else {
// there is no more space
if (i == 0) {
// if the first words exceeds width, just return as the first line will ellipse
return title;
} else {
// If putting word onto a new line, make sure there is no space or new line
// character in the beginning of the current word and just put in the rest of
// the characters.
CharSequence lastCharacters = title.subSequence(stringPtr, title.length());
int beginningLetterType =
Character.getType(Character.codePointAt(lastCharacters,0));
if (beginningLetterType == Character.SPACE_SEPARATOR
|| beginningLetterType == Character.LINE_SEPARATOR) {
lastCharacters = lastCharacters.length() > 1
? lastCharacters.subSequence(1, lastCharacters.length())
: EMPTY;
}
newString.append(NEW_LINE).append(lastCharacters);
return newString.toString();
}
}
if (i >= breakPoints.size()) {
// no need to look forward into the string if we've already finished processing
break;
}
stringPtr = breakPoints.get(i)+1;
}
return newString.toString();
}
@Override
public void cancelLongPress() {
super.cancelLongPress();
@@ -33,6 +33,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.views.ActivityContext;
@@ -140,7 +141,7 @@ public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> ex
protected final OnClickListener mOnIconClickListener;
protected OnLongClickListener mOnIconLongClickListener = INSTANCE_ALL_APPS;
protected OnFocusChangeListener mIconFocusListener;
private final int mExtraHeight;
private final int mExtraTextHeight;
public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater,
AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider) {
@@ -152,7 +153,8 @@ public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> ex
mOnIconClickListener = mActivityContext.getItemOnClickListener();
mAdapterProvider = adapterProvider;
mExtraHeight = res.getDimensionPixelSize(R.dimen.all_apps_height_extra);
mExtraTextHeight = Utilities.calculateTextHeight(
mActivityContext.getDeviceProfile().allAppsIconTextSizePx);
}
/**
@@ -197,7 +199,7 @@ public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> ex
icon.getLayoutParams().height =
mActivityContext.getDeviceProfile().allAppsCellHeightPx;
if (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get()) {
icon.getLayoutParams().height += mExtraHeight;
icon.getLayoutParams().height += mExtraTextHeight;
}
return new ViewHolder(icon);
case VIEW_TYPE_EMPTY_SEARCH:
@@ -113,6 +113,10 @@ public final class FeatureFlags {
public static final BooleanFlag ENABLE_TWOLINE_ALLAPPS = getDebugFlag(270390937,
"ENABLE_TWOLINE_ALLAPPS", false, "Enables two line label inside all apps.");
public static final BooleanFlag ENABLE_TWOLINE_DEVICESEARCH = getDebugFlag(201388851,
"ENABLE_TWOLINE_DEVICESEARCH", false,
"Enable two line label for icons with labels on device search.");
public static final BooleanFlag ENABLE_DEVICE_SEARCH_PERFORMANCE_LOGGING = getReleaseFlag(
270391397, "ENABLE_DEVICE_SEARCH_PERFORMANCE_LOGGING", false,
"Allows on device search in all apps logging");
@@ -16,13 +16,20 @@
package com.android.launcher3.search;
import android.text.TextUtils;
import com.android.launcher3.util.IntArray;
import java.text.Collator;
import java.util.stream.IntStream;
/**
* Utilities for matching query string to target string.
*/
public class StringMatcherUtility {
private static final Character SPACE = ' ';
/**
* Returns {@code true} if {@code query} is a prefix of a substring in {@code target}. How to
* break target to valid substring is defined in the given {@code matcher}.
@@ -58,6 +65,41 @@ public class StringMatcherUtility {
return false;
}
/**
* Returns a list of breakpoints wherever the string contains a break. For example:
* "t-mobile" would have breakpoints at [0, 1]
* "Agar.io" would have breakpoints at [3, 4]
* "LEGO®Builder" would have a breakpoint at [4]
*/
public static IntArray getListOfBreakpoints(CharSequence input, StringMatcher matcher) {
int inputLength = input.length();
if ((inputLength <= 2) || TextUtils.indexOf(input, SPACE) != -1) {
// when there is a space in the string, return a list where the elements are the
// position of the spaces - 1. This is to make the logic consistent where breakpoints
// are placed
return IntArray.wrap(IntStream.range(0, inputLength)
.filter(i -> input.charAt(i) == SPACE)
.map(i -> i - 1)
.toArray());
}
IntArray listOfBreakPoints = new IntArray();
int prevType;
int thisType = Character.getType(Character.codePointAt(input, 0));
int nextType = Character.getType(Character.codePointAt(input, 1));
for (int i = 1; i < inputLength; i++) {
prevType = thisType;
thisType = nextType;
nextType = i < (inputLength - 1)
? Character.getType(Character.codePointAt(input, i + 1))
: Character.UNASSIGNED;
if (matcher.isBreak(thisType, prevType, nextType)) {
// breakpoint is at previous
listOfBreakPoints.add(i-1);
}
}
return listOfBreakPoints;
}
/**
* Performs locale sensitive string comparison using {@link Collator}.
*/
@@ -118,7 +160,11 @@ public class StringMatcherUtility {
}
switch (thisType) {
case Character.UPPERCASE_LETTER:
if (nextType == Character.UPPERCASE_LETTER) {
// takes care of the case where there are consistent uppercase letters as well
// as a special symbol following the capitalize letters for example: LEGO®
if (nextType != Character.UPPERCASE_LETTER && nextType != Character.OTHER_SYMBOL
&& nextType != Character.DECIMAL_DIGIT_NUMBER
&& nextType != Character.UNASSIGNED) {
return true;
}
// Follow through
@@ -15,8 +15,10 @@
*/
package com.android.launcher3.search;
import static com.android.launcher3.search.StringMatcherUtility.getListOfBreakpoints;
import static com.android.launcher3.search.StringMatcherUtility.matches;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -25,6 +27,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.search.StringMatcherUtility.StringMatcher;
import com.android.launcher3.search.StringMatcherUtility.StringMatcherSpace;
import com.android.launcher3.util.IntArray;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -138,4 +141,96 @@ public class StringMatcherUtilityTest {
assertFalse(matches("phant", "elephant", MATCHER_SPACE));
assertFalse(matches("elephants", "elephant", MATCHER_SPACE));
}
@Test
public void testStringWithProperBreaks() {
// empty string
assertEquals(IntArray.wrap(), getListOfBreakpoints("", MATCHER));
// should be "D Dz" that's why breakpoint is at 0
assertEquals(IntArray.wrap(0), getListOfBreakpoints("DDz", MATCHER));
// test all caps and all lower-case
assertEquals(IntArray.wrap(), getListOfBreakpoints("SNKRS", MATCHER));
assertEquals(IntArray.wrap(), getListOfBreakpoints("flutterappflorafy", MATCHER));
assertEquals(IntArray.wrap(), getListOfBreakpoints("LEGO®", MATCHER));
// test camel case
// breakpoint at 9 to be "flutterapp Florafy"
assertEquals(IntArray.wrap(9), getListOfBreakpoints("flutterappFlorafy", MATCHER));
// breakpoint at 4 to be "Metro Zone"
assertEquals(IntArray.wrap(4), getListOfBreakpoints("MetroZone", MATCHER));
// breakpoint at 4,5 to be "metro X Zone"
assertEquals(IntArray.wrap(4,5), getListOfBreakpoints("metroXZone", MATCHER));
// breakpoint at 0 to be "G Pay"
assertEquals(IntArray.wrap(0), getListOfBreakpoints("GPay", MATCHER));
// breakpoint at 4 to be "Whats App"
assertEquals(IntArray.wrap(4), getListOfBreakpoints("WhatsApp", MATCHER));
// breakpoint at 2 to be "aaa A"
assertEquals(IntArray.wrap(2), getListOfBreakpoints("aaaA", MATCHER));
// breakpoint at 4,12,16 to be "Other Launcher Test App"
assertEquals(IntArray.wrap(4,12,16),
getListOfBreakpoints("OtherLauncherTestApp", MATCHER));
// test with TITLECASE_LETTER
// should be "DDz" that's why there are no break points
assertEquals(IntArray.wrap(), getListOfBreakpoints("", MATCHER));
// breakpoint at 0 to be "D DDž"
assertEquals(IntArray.wrap(0), getListOfBreakpoints("DDDž", MATCHER));
// breakpoint at 0 because there is a space to be "Dž DD"
assertEquals(IntArray.wrap(0), getListOfBreakpoints("Dž DD", MATCHER));
// breakpoint at 1 to be "Dw Dz"
assertEquals(IntArray.wrap(1), getListOfBreakpoints("DwDz", MATCHER));
// breakpoint at 0,2 to be "Dw Dz"
assertEquals(IntArray.wrap(0,2), getListOfBreakpoints("wDwDz", MATCHER));
// breakpoint at 1,3 to be "ᾋw Dw Dz"
assertEquals(IntArray.wrap(1,3), getListOfBreakpoints("ᾋwDwDz", MATCHER));
// breakpoint at 0,2,4 to be "ᾋ ᾋw Dw Dz"
assertEquals(IntArray.wrap(0,2,4), getListOfBreakpoints("ᾋᾋwDwDz", MATCHER));
// breakpoint at 0,2,4 to be "ᾋ ᾋw Dw Dz®"
assertEquals(IntArray.wrap(0,2,4), getListOfBreakpoints("ᾋᾋwDwDz®", MATCHER));
// test with numbers and symbols
// breakpoint at 3,11 to be "Test Activity 13"
assertEquals(IntArray.wrap(3,11), getListOfBreakpoints("TestActivity13", MATCHER));
// breakpoint at 3, 4, 12, 13 as the breakpoints are at the dashes
assertEquals(IntArray.wrap(3,4,12,13),
getListOfBreakpoints("Test-Activity-12", MATCHER));
// breakpoint at 1 to be "AA 2"
assertEquals(IntArray.wrap(1), getListOfBreakpoints("AA2", MATCHER));
// breakpoint at 1 to be "AAA 2"
assertEquals(IntArray.wrap(2), getListOfBreakpoints("AAA2", MATCHER));
// breakpoint at 1 to be "ab 2"
assertEquals(IntArray.wrap(1), getListOfBreakpoints("ab2", MATCHER));
// breakpoint at 1,2 to be "el 3 suhwee"
assertEquals(IntArray.wrap(1,2), getListOfBreakpoints("el3suhwee", MATCHER));
// breakpoint at 0,1 as the breakpoints are at '-'
assertEquals(IntArray.wrap(0,1), getListOfBreakpoints("t-mobile", MATCHER));
assertEquals(IntArray.wrap(0,1), getListOfBreakpoints("t-Mobile", MATCHER));
// breakpoint at 0,1,2 as the breakpoints are at '-'
assertEquals(IntArray.wrap(0,1,2), getListOfBreakpoints("t--Mobile", MATCHER));
// breakpoint at 1,2,3 as the breakpoints are at '-'
assertEquals(IntArray.wrap(1,2,3), getListOfBreakpoints("tr--Mobile", MATCHER));
// breakpoint at 3,4 as the breakpoints are at '.'
assertEquals(IntArray.wrap(3,4), getListOfBreakpoints("Agar.io", MATCHER));
assertEquals(IntArray.wrap(3,4), getListOfBreakpoints("Hole.Io", MATCHER));
// breakpoint at 0 to be "µ Torrent®"
assertEquals(IntArray.wrap(0), getListOfBreakpoints("µTorrent®", MATCHER));
// breakpoint at 4 to be "LEGO® Builder"
assertEquals(IntArray.wrap(4), getListOfBreakpoints("LEGO®Builder", MATCHER));
// breakpoint at 4 to be "LEGO® builder"
assertEquals(IntArray.wrap(4), getListOfBreakpoints("LEGO®builder", MATCHER));
// breakpoint at 4 to be "lego® builder"
assertEquals(IntArray.wrap(4), getListOfBreakpoints("lego®builder", MATCHER));
// test string with spaces - where the breakpoints are right before where the spaces are at
assertEquals(IntArray.wrap(3,8), getListOfBreakpoints("HEAD BALL 2", MATCHER));
assertEquals(IntArray.wrap(2,8),
getListOfBreakpoints("OFL Agent Application", MATCHER));
assertEquals(IntArray.wrap(0,2), getListOfBreakpoints("D D z", MATCHER));
assertEquals(IntArray.wrap(6), getListOfBreakpoints("Battery Stats", MATCHER));
assertEquals(IntArray.wrap(5,9,15),
getListOfBreakpoints("System UWB Field Test", MATCHER));
}
}
@@ -0,0 +1,264 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.ui;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TWOLINE_ALLAPPS;
import static org.junit.Assert.assertEquals;
import android.content.Context;
import android.graphics.Typeface;
import android.view.ViewGroup;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.search.StringMatcherUtility;
import com.android.launcher3.util.ActivityContextWrapper;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.TestUtil;
import com.android.launcher3.views.BaseDragLayer;
import org.junit.Before;
import org.junit.Test;
/**
* Unit tests for testing modifyTitleToSupportMultiLine() in BubbleTextView.java
* This class tests a couple of strings and uses the getMaxLines() to determine if the test passes.
* Verifying with getMaxLines() is sufficient since BubbleTextView can only be in one line or
* two lines, and this is enough to ensure whether the string should be specifically wrapped onto
* the second line and to ensure truncation.
*/
public class BubbleTextViewTest {
private static final StringMatcherUtility.StringMatcher
MATCHER = StringMatcherUtility.StringMatcher.getInstance();
private static final int ONE_LINE = 1;
private static final int TWO_LINE = 2;
private static final String TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT = "Battery Stats";
private static final String TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT =
"Battery\nStats";
private static final String TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT =
"flutterappflorafy";
private static final String TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT =
"System UWB Field Test";
private static final String TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT =
"System\nUWB Field Test";
private static final String TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT =
"LEGO®Builder";
private static final String TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT_RESULT =
"LEGO®\nBuilder";
private static final String EMPTY_STRING = "";
private static final int CHAR_CNT = 7;
private BubbleTextView mBubbleTextView;
private ItemInfoWithIcon mItemInfoWithIcon;
private Context mContext;
private int mLimitedWidth;
@Before
public void setUp() throws Exception {
Utilities.enableRunningInTestHarnessForTests();
mContext = new ActivityContextWrapper(getApplicationContext());
mBubbleTextView = new BubbleTextView(mContext);
mBubbleTextView.reset();
mBubbleTextView.setDisplayAllApps();
assertEquals(ONE_LINE, mBubbleTextView.getMaxLines());
BubbleTextView testView = new BubbleTextView(mContext);
testView.setTypeface(Typeface.MONOSPACE);
testView.setText("B");
// calculate the maxWidth of the textView by calculating the width of one monospace
// character * CHAR_CNT
mLimitedWidth =
(int) (testView.getPaint().measureText(testView.getText().toString()) * CHAR_CNT);
// needed otherwise there is a NPE during setText() on checkForRelayout()
mBubbleTextView.setLayoutParams(
new ViewGroup.LayoutParams(mLimitedWidth,
BaseDragLayer.LayoutParams.WRAP_CONTENT));
mItemInfoWithIcon = new ItemInfoWithIcon() {
@Override
public ItemInfoWithIcon clone() {
return null;
}
};
}
@Test
public void testEmptyString_flagOn() {
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
mItemInfoWithIcon.title = EMPTY_STRING;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
assertEquals(ONE_LINE, mBubbleTextView.getMaxLines());
}
@Test
public void testEmptyString_flagOff() {
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
mItemInfoWithIcon.title = EMPTY_STRING;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testStringWithSpaceLongerThanCharLimit_flagOn() {
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
// test string: "Battery Stats"
mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testStringWithSpaceLongerThanCharLimit_flagOff() {
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
// test string: "Battery Stats"
mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringNoSpaceLongerThanCharLimit_flagOn() {
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
// test string: "flutterappflorafy"
mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringNoSpaceLongerThanCharLimit_flagOff() {
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
// test string: "flutterappflorafy"
mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringWithSpaceLongerThanCharLimit_flagOn() {
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
// test string: "System UWB Field Test"
mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringWithSpaceLongerThanCharLimit_flagOff() {
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
// test string: "System UWB Field Test"
mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringSymbolLongerThanCharLimit_flagOn() {
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
// test string: "LEGO®Builder"
mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringSymbolLongerThanCharLimit_flagOff() {
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
// test string: "LEGO®Builder"
mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void modifyTitleToSupportMultiLine_TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT() {
// test string: "Battery Stats"
IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
breakPoints);
assertEquals(TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
}
@Test
public void modifyTitleToSupportMultiLine_TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT() {
// test string: "flutterappflorafy"
IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
breakPoints);
assertEquals(TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, newString);
}
@Test
public void modifyTitleToSupportMultiLine_TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT() {
// test string: "System UWB Field Test"
IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
breakPoints);
assertEquals(TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
}
@Test
public void modifyTitleToSupportMultiLine_TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT() {
// test string: "LEGO®Builder"
IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT, MATCHER);
CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
breakPoints);
assertEquals(TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
}
}