Merge "Support two line text in AllApps/OnDeviceSearch w/ feature flag" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
5ddc6ab4da
@@ -19,7 +19,6 @@
|
||||
android:id="@+id/icon"
|
||||
android:singleLine="false"
|
||||
android:lines="2"
|
||||
android:inputType="textMultiLine"
|
||||
launcher:iconDisplay="all_apps"
|
||||
launcher:centerVertically="true" />
|
||||
|
||||
|
||||
@@ -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("DDz", 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user