Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5ddc6ab4 authored by Brandon Dayauon's avatar Brandon Dayauon Committed by Android (Google) Code Review
Browse files

Merge "Support two line text in AllApps/OnDeviceSearch w/ feature flag" into tm-qpr-dev

parents 0c77edb8 cf88ea1e
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
@@ -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();
+5 −3
Original line number Diff line number Diff line
@@ -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:
+4 −0
Original line number Diff line number Diff line
@@ -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");
+47 −1
Original line number Diff line number Diff line
@@ -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
Loading