Merge "Remove app widget view when widget id is deleted" into tm-qpr-dev
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index a645e58..cf58198 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -221,6 +221,12 @@
return response;
}
+ case TestProtocol.REQUEST_USE_TAPL_WORKSPACE_LAYOUT: {
+ useTestWorkspaceLayout(
+ LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TAPL);
+ return response;
+ }
+
case TestProtocol.REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT: {
useTestWorkspaceLayout(null);
return response;
diff --git a/quickstep/res/values-sw600dp/config.xml b/quickstep/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..e1e442f
--- /dev/null
+++ b/quickstep/res/values-sw600dp/config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<!-- Applies to large tablet screens portrait -->
+<resources>
+ <!-- Taskbar -->
+ <!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
+ <bool name="start_align_taskbar">true</bool>
+</resources>
\ No newline at end of file
diff --git a/quickstep/res/values-sw720dp-land/config.xml b/quickstep/res/values-sw720dp-land/config.xml
new file mode 100644
index 0000000..bf0f9ad
--- /dev/null
+++ b/quickstep/res/values-sw720dp-land/config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<!-- Applies to large tablet screens landscape -->
+<resources>
+ <!-- Taskbar -->
+ <!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
+ <bool name="start_align_taskbar">false</bool>
+</resources>
\ No newline at end of file
diff --git a/quickstep/res/values-sw720dp/config.xml b/quickstep/res/values-sw720dp/config.xml
new file mode 100644
index 0000000..e1e442f
--- /dev/null
+++ b/quickstep/res/values-sw720dp/config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<!-- Applies to large tablet screens portrait -->
+<resources>
+ <!-- Taskbar -->
+ <!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
+ <bool name="start_align_taskbar">true</bool>
+</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index d581582..b61bdfb 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -51,4 +51,8 @@
</item>
<string name="setup_wizard_pkg" translatable="false" />
+
+ <!-- Taskbar -->
+ <!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
+ <bool name="start_align_taskbar">false</bool>
</resources>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 11b07e8..e66856a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -94,6 +94,8 @@
private float mTransientTaskbarAllAppsButtonTranslationXOffset;
+ private final boolean mStartAlignTaskbar;
+
public TaskbarView(@NonNull Context context) {
this(context, null);
}
@@ -121,6 +123,8 @@
resources.getDimension(isTransientTaskbar
? R.dimen.transient_taskbar_all_apps_button_translation_x_offset
: R.dimen.taskbar_all_apps_button_translation_x_offset);
+ mStartAlignTaskbar = mActivityContext.isThreeButtonNav()
+ && resources.getBoolean(R.bool.start_align_taskbar);
int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
int actualIconSize = mActivityContext.getDeviceProfile().iconSizePx;
@@ -372,10 +376,22 @@
boolean needMoreSpaceForNav = layoutRtl ?
navSpaceNeeded > (iconEnd - spaceNeeded) :
iconEnd > (right - navSpaceNeeded);
- if (needMoreSpaceForNav) {
- int offset = layoutRtl ?
- navSpaceNeeded - (iconEnd - spaceNeeded) :
- (right - navSpaceNeeded) - iconEnd;
+
+ if (mStartAlignTaskbar) {
+ // Taskbar is aligned to the start
+ int startSpacingPx = deviceProfile.inlineNavButtonsEndSpacingPx;
+
+ if (layoutRtl) {
+ iconEnd = right - startSpacingPx;
+ } else {
+ iconEnd = startSpacingPx + spaceNeeded;
+ }
+ } else if (needMoreSpaceForNav) {
+ // Add offset to account for nav bar when taskbar is centered
+ int offset = layoutRtl
+ ? navSpaceNeeded - (iconEnd - spaceNeeded)
+ : (right - navSpaceNeeded) - iconEnd;
+
iconEnd += offset;
}
diff --git a/res/layout/all_apps_icon_twoline.xml b/res/layout/all_apps_icon_twoline.xml
index 54c7147..b0d02c1 100644
--- a/res/layout/all_apps_icon_twoline.xml
+++ b/res/layout/all_apps_icon_twoline.xml
@@ -19,7 +19,6 @@
android:id="@+id/icon"
android:singleLine="false"
android:lines="2"
- android:inputType="textMultiLine"
launcher:iconDisplay="all_apps"
launcher:centerVertically="true" />
diff --git a/res/xml/default_tapl_test_workspace.xml b/res/xml/default_tapl_test_workspace.xml
new file mode 100644
index 0000000..24d76f3
--- /dev/null
+++ b/res/xml/default_tapl_test_workspace.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<!-- Split display specific version of Launcher3/res/xml/default_workspace_4x4.xml -->
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+
+ <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+ <favorite
+ launcher:container="-101"
+ launcher:screen="0"
+ launcher:x="0"
+ launcher:y="0"
+ launcher:className="com.google.android.apps.chrome.Main"
+ launcher:packageName="com.android.chrome" />
+
+</favorites>
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index eb6d096..9d5b08e 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -2,6 +2,7 @@
import static android.os.Process.myUserHandle;
+import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
@@ -51,10 +52,10 @@
*/
@WorkerThread
public static void restoreAppWidgetIds(Context context, int[] oldWidgetIds, int[] newWidgetIds,
- @NonNull LauncherWidgetHolder holder) {
+ @NonNull AppWidgetHost host) {
if (WidgetsModel.GO_DISABLE_WIDGETS) {
Log.e(TAG, "Skipping widget ID remap as widgets not supported");
- holder.deleteHost();
+ host.deleteHost();
return;
}
if (!RestoreDbTask.isPending(context)) {
@@ -63,7 +64,7 @@
Log.e(TAG, "Skipping widget ID remap as DB already in use");
for (int widgetId : newWidgetIds) {
Log.d(TAG, "Deleting widgetId: " + widgetId);
- holder.deleteAppWidgetId(widgetId);
+ host.deleteAppWidgetId(widgetId);
}
return;
}
@@ -100,7 +101,7 @@
try {
if (!cursor.moveToFirst()) {
// The widget no long exists.
- holder.deleteAppWidgetId(newWidgetIds[i]);
+ host.deleteAppWidgetId(newWidgetIds[i]);
}
} finally {
cursor.close();
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 3eb03ed..801b740 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -52,8 +52,10 @@
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.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 @@
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 @@
private FastBitmapDrawable mIcon;
private boolean mCenterVertically;
- protected final int mDisplay;
+ protected int mDisplay;
private final CheckLongPressHelper mLongPressHelper;
@@ -255,6 +267,8 @@
mDotParams.scale = 0f;
mForceHideDot = false;
setBackground(null);
+ setSingleLine(true);
+ setMaxLines(1);
setTag(null);
if (mIconLoadRequest != null) {
@@ -382,8 +396,15 @@
}
@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 @@
}
}
+ /** 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 @@
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 @@
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();
@@ -717,7 +832,7 @@
|| info.hasPromiseIconUi()
|| (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0
|| (ENABLE_DOWNLOAD_APP_UX_V2.get() && icon != null)) {
- updateProgressBarUi(icon);
+ updateProgressBarUi(info.getProgressLevel() == 100 ? icon : null);
}
}
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 87ee4f6..d992ee0 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -193,7 +193,7 @@
private final int mMinHotseatIconSpacePx;
private final int mMinHotseatQsbWidthPx;
private final int mMaxHotseatIconSpacePx;
- private final int mInlineNavButtonsEndSpacingPx;
+ public final int inlineNavButtonsEndSpacingPx;
// Bottom sheets
public int bottomSheetTopPadding;
@@ -490,7 +490,7 @@
hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
updateHotseatSizes(pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
if (areNavButtonsInline && !isPhone) {
- mInlineNavButtonsEndSpacingPx =
+ inlineNavButtonsEndSpacingPx =
res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing);
/*
* 3 nav buttons +
@@ -499,9 +499,9 @@
*/
hotseatBarEndOffset = 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
+ 2 * res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween)
- + mInlineNavButtonsEndSpacingPx;
+ + inlineNavButtonsEndSpacingPx;
} else {
- mInlineNavButtonsEndSpacingPx = 0;
+ inlineNavButtonsEndSpacingPx = 0;
hotseatBarEndOffset = 0;
}
@@ -662,7 +662,7 @@
}
// The side space with inline buttons should be what is defined in InvariantDeviceProfile
- int sideSpacePx = mInlineNavButtonsEndSpacingPx;
+ int sideSpacePx = inlineNavButtonsEndSpacingPx;
int maxHotseatWidthPx = availableWidthPx - sideSpacePx - hotseatBarEndOffset;
int maxHotseatIconsWidthPx = maxHotseatWidthPx - (isQsbInline ? hotseatQsbWidth : 0);
hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidthPx,
@@ -1320,7 +1320,7 @@
int endSpacing;
// Hotseat aligns to the left with nav buttons
if (hotseatBarEndOffset > 0) {
- startSpacing = mInlineNavButtonsEndSpacingPx;
+ startSpacing = inlineNavButtonsEndSpacingPx;
endSpacing = availableWidthPx - hotseatWidth - startSpacing + hotseatBorderSpace;
} else {
startSpacing = (availableWidthPx - hotseatWidth) / 2;
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 767c3d9..e688709 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -107,6 +107,7 @@
private static final int TEST_WORKSPACE_LAYOUT_RES_XML = R.xml.default_test_workspace;
private static final int TEST2_WORKSPACE_LAYOUT_RES_XML = R.xml.default_test2_workspace;
+ private static final int TAPL_WORKSPACE_LAYOUT_RES_XML = R.xml.default_tapl_test_workspace;
static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
@@ -410,6 +411,9 @@
case LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TEST2:
mDefaultWorkspaceLayoutOverride = TEST2_WORKSPACE_LAYOUT_RES_XML;
break;
+ case LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TAPL:
+ mDefaultWorkspaceLayoutOverride = TAPL_WORKSPACE_LAYOUT_RES_XML;
+ break;
default:
mDefaultWorkspaceLayoutOverride = 0;
break;
@@ -550,39 +554,42 @@
Log.d(TAG, "loading default workspace");
LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
- AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHolder);
- if (loader == null) {
- loader = AutoInstallsLayout.get(getContext(), widgetHolder, mOpenHelper);
- }
- if (loader == null) {
- final Partner partner = Partner.get(getContext().getPackageManager());
- if (partner != null) {
- int workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT);
- if (workspaceResId != 0) {
- loader = new DefaultLayoutParser(getContext(), widgetHolder,
- mOpenHelper, partner.getResources(), workspaceResId);
+ try {
+ AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHolder);
+ if (loader == null) {
+ loader = AutoInstallsLayout.get(getContext(), widgetHolder, mOpenHelper);
+ }
+ if (loader == null) {
+ final Partner partner = Partner.get(getContext().getPackageManager());
+ if (partner != null) {
+ int workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT);
+ if (workspaceResId != 0) {
+ loader = new DefaultLayoutParser(getContext(), widgetHolder,
+ mOpenHelper, partner.getResources(), workspaceResId);
+ }
}
}
- }
- final boolean usingExternallyProvidedLayout = loader != null;
- if (loader == null) {
- loader = getDefaultLayoutParser(widgetHolder);
- }
+ final boolean usingExternallyProvidedLayout = loader != null;
+ if (loader == null) {
+ loader = getDefaultLayoutParser(widgetHolder);
+ }
- // There might be some partially restored DB items, due to buggy restore logic in
- // previous versions of launcher.
- mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
- // Populate favorites table with initial favorites
- if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
- && usingExternallyProvidedLayout) {
- // Unable to load external layout. Cleanup and load the internal layout.
+ // There might be some partially restored DB items, due to buggy restore logic in
+ // previous versions of launcher.
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
- mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
- getDefaultLayoutParser(widgetHolder));
+ // Populate favorites table with initial favorites
+ if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
+ && usingExternallyProvidedLayout) {
+ // Unable to load external layout. Cleanup and load the internal layout.
+ mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
+ mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
+ getDefaultLayoutParser(widgetHolder));
+ }
+ clearFlagEmptyDbCreated();
+ } finally {
+ widgetHolder.destroy();
}
- clearFlagEmptyDbCreated();
- widgetHolder.destroy();
}
}
@@ -957,8 +964,6 @@
allWidgets = holder.getAppWidgetIds();
} catch (IncompatibleClassChangeError e) {
Log.e(TAG, "getAppWidgetIds not supported", e);
- // Necessary to destroy the holder to free up possible activity context
- holder.destroy();
return;
}
final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db,
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 66ea616..cef00d9 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -389,6 +389,7 @@
"set_use_test_workspace_layout_flag";
public static final String ARG_DEFAULT_WORKSPACE_LAYOUT_TEST = "default_test_workspace";
public static final String ARG_DEFAULT_WORKSPACE_LAYOUT_TEST2 = "default_test2_workspace";
+ public static final String ARG_DEFAULT_WORKSPACE_LAYOUT_TAPL = "default_tapl_workspace";
public static final String METHOD_CLEAR_USE_TEST_WORKSPACE_LAYOUT_FLAG =
"clear_use_test_workspace_layout_flag";
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 7040de5..8fa4276 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -33,6 +33,7 @@
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 @@
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 @@
mOnIconClickListener = mActivityContext.getItemOnClickListener();
mAdapterProvider = adapterProvider;
- mExtraHeight = res.getDimensionPixelSize(R.dimen.all_apps_height_extra);
+ mExtraTextHeight = Utilities.calculateTextHeight(
+ mActivityContext.getDeviceProfile().allAppsIconTextSizePx);
}
/**
@@ -197,7 +199,7 @@
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:
diff --git a/src/com/android/launcher3/anim/AnimatedFloat.java b/src/com/android/launcher3/anim/AnimatedFloat.java
index b73621d..2380af4 100644
--- a/src/com/android/launcher3/anim/AnimatedFloat.java
+++ b/src/com/android/launcher3/anim/AnimatedFloat.java
@@ -55,6 +55,11 @@
mUpdateCallback = updateCallback;
}
+ public AnimatedFloat(Runnable updateCallback, float initialValue) {
+ this(updateCallback);
+ value = initialValue;
+ }
+
/**
* Returns an animation from the current value to the given value.
*/
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 6bb2a0f..d1aaef1 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -113,6 +113,10 @@
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");
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 2a452be..3a5ef10 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -21,8 +21,10 @@
import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS;
import static com.android.launcher3.LauncherPrefs.RESTORE_DEVICE;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
import android.app.backup.BackupManager;
+import android.appwidget.AppWidgetHost;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -49,7 +51,6 @@
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LogConfig;
-import com.android.launcher3.widget.LauncherWidgetHolder;
import java.io.InvalidObjectException;
import java.util.Arrays;
@@ -354,12 +355,11 @@
private void restoreAppWidgetIdsIfExists(Context context) {
LauncherPrefs lp = LauncherPrefs.get(context);
if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) {
- LauncherWidgetHolder holder = LauncherWidgetHolder.newInstance(context);
+ AppWidgetHost host = new AppWidgetHost(context, APPWIDGET_HOST_ID);
AppWidgetsRestoredReceiver.restoreAppWidgetIds(context,
IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(),
IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(),
- holder);
- holder.destroy();
+ host);
} else {
FileLog.d(TAG, "No app widget ids to restore.");
}
diff --git a/src/com/android/launcher3/search/StringMatcherUtility.java b/src/com/android/launcher3/search/StringMatcherUtility.java
index c66f3a1..28fc4f0 100644
--- a/src/com/android/launcher3/search/StringMatcherUtility.java
+++ b/src/com/android/launcher3/search/StringMatcherUtility.java
@@ -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}.
@@ -59,6 +66,41 @@
}
/**
+ * 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}.
*/
public static class StringMatcher {
@@ -118,7 +160,11 @@
}
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
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index e2f1c04..cb6a46c 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -76,6 +76,7 @@
};
protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
protected static final float TRANSLATION_SHIFT_OPENED = 0f;
+ private static final float VIEW_NO_SCALE = 1f;
protected final T mActivityContext;
@@ -93,7 +94,8 @@
protected @Nullable OnCloseListener mOnCloseBeginListener;
protected List<OnCloseListener> mOnCloseListeners = new ArrayList<>();
- private final AnimatedFloat mSlidInViewScale = new AnimatedFloat(this::onScaleProgressChanged);
+ private final AnimatedFloat mSlideInViewScale =
+ new AnimatedFloat(this::onScaleProgressChanged, VIEW_NO_SCALE);
private boolean mIsBackProgressing;
@Nullable private Drawable mContentBackground;
@@ -184,12 +186,12 @@
float deceleratedProgress =
Interpolators.PREDICTIVE_BACK_DECELERATED_EASE.getInterpolation(progress);
mIsBackProgressing = progress > 0f;
- mSlidInViewScale.updateValue(PREDICTIVE_BACK_MIN_SCALE
+ mSlideInViewScale.updateValue(PREDICTIVE_BACK_MIN_SCALE
+ (1 - PREDICTIVE_BACK_MIN_SCALE) * (1 - deceleratedProgress));
}
private void onScaleProgressChanged() {
- float scaleProgress = mSlidInViewScale.value;
+ float scaleProgress = mSlideInViewScale.value;
SCALE_PROPERTY.set(this, scaleProgress);
setClipChildren(!mIsBackProgressing);
mContent.setClipChildren(!mIsBackProgressing);
@@ -209,7 +211,7 @@
}
protected void animateSlideInViewToNoScale() {
- mSlidInViewScale.animateToValue(1f)
+ mSlideInViewScale.animateToValue(1f)
.setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS)
.start();
}
@@ -236,8 +238,8 @@
/** Return extra space revealed during predictive back animation. */
@Px
protected int getBottomOffsetPx() {
- return (int) (getMeasuredHeight()
- * (1 - PREDICTIVE_BACK_MIN_SCALE) / 2);
+ final int height = getMeasuredHeight();
+ return (int) ((height / PREDICTIVE_BACK_MIN_SCALE - height) / 2);
}
/**
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index bea7517..8e67eb1 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -322,13 +322,6 @@
}
/**
- * Delete the host
- */
- public void deleteHost() {
- mWidgetHost.deleteHost();
- }
-
- /**
* @return The app widget ids
*/
@NonNull
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index c69ec2c..f036b3e 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -107,6 +107,7 @@
public static final String REQUEST_CLEAR_DATA = "clear-data";
public static final String REQUEST_USE_TEST_WORKSPACE_LAYOUT = "use-test-workspace-layout";
public static final String REQUEST_USE_TEST2_WORKSPACE_LAYOUT = "use-test2-workspace-layout";
+ public static final String REQUEST_USE_TAPL_WORKSPACE_LAYOUT = "use-tapl-workspace-layout";
public static final String REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT =
"use-default-workspace-layout";
public static final String REQUEST_HOTSEAT_ICON_NAMES = "get-hotseat-icon-names";
diff --git a/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java b/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
index 3b53255..0ba46f4 100644
--- a/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
+++ b/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
@@ -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 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 @@
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));
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
new file mode 100644
index 0000000..528f7ac
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -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);
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 5f516eb..fef1708 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -225,6 +225,10 @@
@Test
@ScreenRecord // b/202433017
public void testWorkspace() throws Exception {
+ // Make sure there is an instance of chrome on the hotseat
+ mLauncher.useTaplWorkspaceLayoutOnReload();
+ clearLauncherData();
+
final Workspace workspace = mLauncher.getWorkspace();
// Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 52994a5..5c4b707 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1860,6 +1860,15 @@
getTestInfo(TestProtocol.REQUEST_USE_TEST2_WORKSPACE_LAYOUT);
}
+
+ /**
+ * Reloads the workspace with a test layout that includes the chrome Activity app icon on the
+ * hotseat.
+ */
+ public void useTaplWorkspaceLayoutOnReload() {
+ getTestInfo(TestProtocol.REQUEST_USE_TAPL_WORKSPACE_LAYOUT);
+ }
+
/** Reloads the workspace with the default layout defined by the user's grid size selection. */
public void useDefaultWorkspaceLayoutOnReload() {
getTestInfo(TestProtocol.REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT);