Merge "Reland slow gesture callback handling" into 24D1-dev
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index ecd7e86..a9a8495 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -188,19 +188,21 @@
}
flag {
- name: "grid_migration_fix"
+ name: "enable_grid_migration_fix"
namespace: "launcher"
description: "Keep items in place when migrating to a bigger grid"
bug: "325286145"
+ is_fixed_read_only: true
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
- name: "narrow_grid_restore"
+ name: "enable_narrow_grid_restore"
namespace: "launcher"
description: "Using only the most recent workspace when restoring to avoid confusion."
+ is_fixed_read_only: true
bug: "325285743"
metadata {
purpose: PURPOSE_BUGFIX
diff --git a/quickstep/res/layout/taskbar_all_apps_button.xml b/quickstep/res/layout/taskbar_all_apps_button.xml
index c50db2e..94596cb 100644
--- a/quickstep/res/layout/taskbar_all_apps_button.xml
+++ b/quickstep/res/layout/taskbar_all_apps_button.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2022 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.
@@ -15,10 +14,9 @@
-->
<!-- Note: The actual size will match the taskbar icon sizes in TaskbarView#onLayout(). -->
-<com.android.launcher3.views.IconButtonView
- xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher3.views.IconButtonView xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/BaseIcon.Workspace.Taskbar"
android:layout_width="@dimen/taskbar_icon_min_touch_size"
android:layout_height="@dimen/taskbar_icon_min_touch_size"
- android:contentDescription="@string/all_apps_button_label"
android:backgroundTint="@android:color/transparent"
- />
+ android:contentDescription="@string/all_apps_button_label" />
diff --git a/quickstep/res/layout/taskbar_divider.xml b/quickstep/res/layout/taskbar_divider.xml
index 0a92fa9..330f85f 100644
--- a/quickstep/res/layout/taskbar_divider.xml
+++ b/quickstep/res/layout/taskbar_divider.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2023 The Android Open Source Project
+<?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.
@@ -13,9 +12,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.views.IconButtonView
- xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher3.views.IconButtonView xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/BaseIcon.Workspace.Taskbar"
android:layout_width="@dimen/taskbar_icon_min_touch_size"
android:layout_height="@dimen/taskbar_icon_min_touch_size"
- android:contentDescription="@string/taskbar_divider_a11y_title"
- android:backgroundTint="@android:color/transparent" />
\ No newline at end of file
+ android:backgroundTint="@android:color/transparent"
+ android:contentDescription="@string/taskbar_divider_a11y_title" />
\ No newline at end of file
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 1ddcdf2..6c44867 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -124,7 +124,7 @@
<string name="taskbar_button_ime_switcher" msgid="1730244360907588541">"IME ସ୍ୱିଚର"</string>
<string name="taskbar_button_recents" msgid="7273376136216613134">"ବର୍ତ୍ତମାନର"</string>
<string name="taskbar_button_notifications" msgid="7471740351507357318">"ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ"</string>
- <string name="taskbar_button_quick_settings" msgid="227662894293189391">"କ୍ୱିକ ସେଟିଂସ"</string>
+ <string name="taskbar_button_quick_settings" msgid="227662894293189391">"କୁଇକ ସେଟିଂସ"</string>
<string name="taskbar_a11y_title" msgid="6432169809852243110">"ଟାସ୍କବାର"</string>
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ଟାସ୍କବାର ଦେଖାଯାଇଛି"</string>
<string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ଟାସ୍କବାର ଲୁଚାଯାଇଛି"</string>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 67b41c6..81bade3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -32,6 +32,7 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.util.AttributeSet;
+import android.view.DisplayCutout;
import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -134,7 +135,7 @@
int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
int actualIconSize = mActivityContext.getDeviceProfile().taskbarIconSize;
- if (enableTaskbarPinning()) {
+ if (enableTaskbarPinning() && !mActivityContext.isThreeButtonNav()) {
DeviceProfile deviceProfile = mActivityContext.getTransientTaskbarDeviceProfile();
actualIconSize = deviceProfile.taskbarIconSize;
}
@@ -472,6 +473,29 @@
iconEnd = centerAlignIconEnd + offset;
}
+ // Currently, we support only one device with display cutout and we only are concern about
+ // it when the bottom rect is present and non empty
+ DisplayCutout displayCutout = getDisplay().getCutout();
+ if (displayCutout != null && !displayCutout.getBoundingRectBottom().isEmpty()) {
+ Rect cutoutBottomRect = displayCutout.getBoundingRectBottom();
+ // when cutout present at the bottom of screen align taskbar icons to cutout offset
+ // if taskbar icon overlaps with cutout
+ int taskbarIconLeftBound = iconEnd - spaceNeeded;
+ int taskbarIconRightBound = iconEnd;
+
+ boolean doesTaskbarIconsOverlapWithCutout =
+ taskbarIconLeftBound <= cutoutBottomRect.centerX()
+ && cutoutBottomRect.centerX() <= taskbarIconRightBound;
+
+ if (doesTaskbarIconsOverlapWithCutout) {
+ if (!layoutRtl) {
+ iconEnd = spaceNeeded + cutoutBottomRect.width();
+ } else {
+ iconEnd = right - cutoutBottomRect.width();
+ }
+ }
+ }
+
sTmpRect.set(mIconLayoutBounds);
// Layout the children
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 1f7f0a7..5d0eac3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -337,11 +337,6 @@
private void updateTaskbarIconTranslationXForPinning() {
View[] iconViews = mTaskbarView.getIconViews();
float scale = mTaskbarIconTranslationXForPinning.value;
- float taskbarCenterX =
- mTaskbarView.getLeft() + (mTaskbarView.getRight() - mTaskbarView.getLeft()) / 2.0f;
-
- float finalMarginScale = mapRange(scale, 0f, mTransientIconSize - mPersistentIconSize);
-
float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension(
mTaskbarView.getAllAppsButtonTranslationXOffset(true));
float persistentTaskbarAllAppsOffset = mActivity.getResources().getDimension(
@@ -354,6 +349,17 @@
allAppIconTranslateRange *= -1;
}
+ if (mActivity.isThreeButtonNav()) {
+ ((IconButtonView) mTaskbarView.getAllAppsButtonView())
+ .setTranslationXForTaskbarAllAppsIcon(allAppIconTranslateRange);
+ return;
+ }
+
+ float taskbarCenterX =
+ mTaskbarView.getLeft() + (mTaskbarView.getRight() - mTaskbarView.getLeft()) / 2.0f;
+
+ float finalMarginScale = mapRange(scale, 0f, mTransientIconSize - mPersistentIconSize);
+
float halfIconCount = iconViews.length / 2.0f;
for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) {
View iconView = iconViews[iconIndex];
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
index 5c57a01..a385502 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -28,35 +28,39 @@
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.systemui.shared.rotation.RotationButton
-/**
- * Layoutter for rendering task bar in large screen, both in 3-button and gesture nav mode.
- */
+/** Layoutter for rendering task bar in large screen, both in 3-button and gesture nav mode. */
class TaskbarNavLayoutter(
- resources: Resources,
- navBarContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup,
- imeSwitcher: ImageView?,
- rotationButton: RotationButton?,
- a11yButton: ImageView?,
- space: Space?
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ rotationButton: RotationButton?,
+ a11yButton: ImageView?,
+ space: Space?
) :
AbstractNavButtonLayoutter(
- resources,
- navBarContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton,
+ space
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
// Add spacing after the end of the last nav button
- var navMarginEnd = resources
- .getDimension(context.deviceProfile.inv.inlineNavButtonsEndSpacing)
- .toInt()
+ var navMarginEnd =
+ resources.getDimension(context.deviceProfile.inv.inlineNavButtonsEndSpacing).toInt()
+
+ val cutout = context.display.cutout
+ val bottomRect = cutout?.boundingRectBottom
+ if (bottomRect != null && !bottomRect.isEmpty) {
+ navMarginEnd = bottomRect.width()
+ }
+
val contextualWidth = endContextualContainer.width
// If contextual buttons are showing, we check if the end margin is enough for the
// contextual button to be showing - if not, move the nav buttons over a smidge
@@ -65,8 +69,11 @@
navMarginEnd += resources.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2
}
- val navButtonParams = FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ val navButtonParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
navButtonParams.apply {
gravity = Gravity.END or Gravity.CENTER_VERTICAL
marginEnd = navMarginEnd
diff --git a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt b/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
index b1ba4c6..a388510 100644
--- a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
@@ -42,6 +42,8 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(510)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
assertThat(dp.hotseatBorderSpace).isEqualTo(70)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1445)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(150)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(580)
@@ -64,6 +66,8 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(510)
assertThat(dp.numShownHotseatIcons).isEqualTo(4)
assertThat(dp.hotseatBorderSpace).isEqualTo(40)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1080)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(150)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(550)
@@ -85,6 +89,8 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(705)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
assertThat(dp.hotseatBorderSpace).isEqualTo(54)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1468)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(231)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(759)
@@ -100,22 +106,21 @@
@Test
fun nav_buttons_dont_interfere_with_required_hotseat_width() {
initializeVarsForTablet(isGestureMode = false, isLandscape = true)
- inv?.apply {
- hotseatColumnSpan = IntArray(4) { 4 }
- inlineQsb = BooleanArray(4) { false }
- }
+ inv?.apply { inlineQsb = BooleanArray(4) { false } }
val dp = newDP()
dp.isTaskbarPresentInApps = true
assertThat(dp.hotseatBarEndOffset).isEqualTo(660)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
assertThat(dp.hotseatBorderSpace).isEqualTo(100)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1975)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(300)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(1040)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1223)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1965)
}
/** This is a case when after setting the hotseat, the QSB width needs to be changed to fit */
@@ -128,13 +133,15 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(660)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
- assertThat(dp.hotseatBorderSpace).isEqualTo(36)
+ assertThat(dp.hotseatBorderSpace).isEqualTo(34)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(4)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1179)
- assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(864)
- assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(696)
+ assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(876)
+ assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(694)
assertThat(dp.isQsbInline).isTrue()
- assertThat(dp.hotseatQsbWidth).isEqualTo(528)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(542)
}
/**
@@ -151,6 +158,8 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(660)
assertThat(dp.numShownHotseatIcons).isEqualTo(5)
assertThat(dp.hotseatBorderSpace).isEqualTo(36)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(4)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1095)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(816)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(700)
@@ -158,4 +167,23 @@
assertThat(dp.isQsbInline).isTrue()
assertThat(dp.hotseatQsbWidth).isEqualTo(480)
}
+
+ @Test
+ fun increase_span_when_space_between_icons_is_less_than_minimum() {
+ initializeVarsForTwoPanel(isGestureMode = false, isLandscape = false, rows = 5, cols = 5)
+ val dp = newDP()
+ dp.isTaskbarPresentInApps = true
+
+ assertThat(dp.hotseatBarEndOffset).isEqualTo(600)
+ assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+ assertThat(dp.hotseatBorderSpace).isEqualTo(48)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(8)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1383)
+
+ assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(126)
+ assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(652)
+
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1372)
+ }
}
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 8e77fec..a605549 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -119,7 +119,7 @@
<string name="allow_rotation_title" msgid="7222049633713050106">"Дазволіць паварот галоўнага экрана"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Пры павароце тэлефона"</string>
<string name="notification_dots_title" msgid="9062440428204120317">"Значкі апавяшчэнняў"</string>
- <string name="notification_dots_desc_on" msgid="1679848116452218908">"Укл."</string>
+ <string name="notification_dots_desc_on" msgid="1679848116452218908">"Уключана"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"Выкл."</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"Патрабуецца доступ да апавяшчэнняў"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"Каб паказваліся значкі апавяшчэнняў, уключыце апавяшчэнні праграм для <xliff:g id="NAME">%1$s</xliff:g>"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 6c9c7fb..553fc6c 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -83,7 +83,7 @@
<string name="all_apps_button_work_label" msgid="7270707118948892488">"ಕೆಲಸದ ಅಪ್ಲಿಕೇಶನ್ಗಳ ಪಟ್ಟಿ"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"ತೆಗೆದುಹಾಕಿ"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"ಅನ್ಇನ್ಸ್ಟಾಲ್"</string>
- <string name="app_info_drop_target_label" msgid="692894985365717661">"ಅಪ್ಲಿಕೇಶನ್ ಮಾಹಿತಿ"</string>
+ <string name="app_info_drop_target_label" msgid="692894985365717661">"ಆ್ಯಪ್ ಮಾಹಿತಿ"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ಖಾಸಗಿಯಾಗಿ ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ಸ್ಥಾಪಿಸಿ"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"ಆ್ಯಪ್ ಅನ್ನು ಸೂಚಿಸಬೇಡಿ"</string>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index cc1f09e..eda9b2d 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -40,6 +40,7 @@
<!-- Hotseat -->
<dimen name="dynamic_grid_hotseat_side_padding">0dp</dimen>
<dimen name="spring_loaded_hotseat_top_margin">65dp</dimen>
+ <dimen name="min_hotseat_icon_space">17dp</dimen>
<!-- Dragging -->
<dimen name="drop_target_top_margin">64dp</dimen>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index b09903d..b0f15b8f7 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -201,17 +201,6 @@
defaults to 2 * numHotseatIcons -->
<attr name="numExtendedHotseatIcons" format="integer" />
- <!-- alignment of hotseat to the grid.
- Not applicable for 3 button mode when taskbar is enabled -->
- <!-- defaults to numColumns, if not specified -->
- <attr name="hotseatColumnSpan" format="integer" />
- <!-- defaults to numColumns, if not specified -->
- <attr name="hotseatColumnSpanLandscape" format="integer" />
- <!-- defaults to numColumns, if not specified -->
- <attr name="hotseatColumnSpanTwoPanelLandscape" format="integer" />
- <!-- defaults to numColumns, if not specified -->
- <attr name="hotseatColumnSpanTwoPanelPortrait" format="integer" />
-
<!-- Spacing to have at the end of the nav buttons in large screen 3 button nav,
defaults to @dimen/taskbar_button_margin_default -->
<attr name="inlineNavButtonsEndSpacing" format="reference" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9f4d308..73ebaa8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -437,6 +437,8 @@
<string name="work_profile_edu_work_apps">Work apps are badged and visible to your IT admin</string>
<!-- Action label to finish work profile edu-->
<string name="work_profile_edu_accept">Got it</string>
+ <!-- Info icon unicode for alpha scroller when work edu card is present -->
+ <string name="work_profile_edu_section" translatable="false">\u24D8</string>
<!--- heading shown when user opens work apps tab while work apps are paused -->
<string name="work_apps_paused_title">Work apps are paused</string>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index c9a44a1..1d0dbff 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -168,7 +168,6 @@
launcher:numFolderRows="3"
launcher:numFolderColumns="3"
launcher:numHotseatIcons="6"
- launcher:hotseatColumnSpanLandscape="4"
launcher:numAllAppsColumns="6"
launcher:isScalable="true"
launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_6_5"
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 4ca20d6..43d1213 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -237,7 +237,7 @@
defaultIconSize = getResources().getDimensionPixelSize(
R.dimen.search_row_small_icon_size);
} else if (mDisplay == DISPLAY_TASKBAR) {
- defaultIconSize = mDeviceProfile.iconSizePx;
+ defaultIconSize = mDeviceProfile.taskbarIconSize;
} else {
// widget_selection or shortcut_popup
defaultIconSize = mDeviceProfile.iconSizePx;
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f96d59e..35caf34 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -46,6 +46,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.core.content.res.ResourcesCompat;
import com.android.launcher3.CellLayout.ContainerType;
@@ -210,6 +211,8 @@
// Hotseat
public int numShownHotseatIcons;
public int hotseatCellHeightPx;
+ private int mHotseatColumnSpan;
+ private int mHotseatWidthPx; // not used in vertical bar layout
public final boolean areNavButtonsInline;
// In portrait: size = height, in landscape: size = width
public int hotseatBarSizePx;
@@ -544,6 +547,7 @@
areNavButtonsInline = isTaskbarPresent && !isGestureMode;
numShownHotseatIcons =
isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons;
+ mHotseatColumnSpan = inv.numColumns;
numShownAllAppsColumns =
isTwoPanels ? inv.numDatabaseAllAppsColumns : inv.numAllAppsColumns;
@@ -811,8 +815,7 @@
- hotseatBorderSpace * numShownHotseatIcons
- iconExtraSpacePx;
} else {
- int columns = inv.hotseatColumnSpan[mTypeIndex];
- return getIconToIconWidthForColumns(columns) - iconExtraSpacePx;
+ return getIconToIconWidthForColumns(mHotseatColumnSpan) - iconExtraSpacePx;
}
}
@@ -883,10 +886,31 @@
public void recalculateHotseatWidthAndBorderSpace() {
if (!mIsScalableGrid) return;
- int columns = inv.hotseatColumnSpan[mTypeIndex];
- float hotseatWidthPx = getIconToIconWidthForColumns(columns);
- hotseatBorderSpace = calculateHotseatBorderSpace(hotseatWidthPx, /* numExtraBorder= */ 0);
+ updateHotseatWidthAndBorderSpace(inv.numColumns);
+ int numWorkspaceColumns = getPanelCount() * inv.numColumns;
+ if (isTwoPanels) {
+ updateHotseatWidthAndBorderSpace(inv.numDatabaseHotseatIcons);
+ // If hotseat doesn't fit with current width, increase column span to fit by multiple
+ // of 2.
+ while (hotseatBorderSpace < mMinHotseatIconSpacePx
+ && mHotseatColumnSpan < numWorkspaceColumns) {
+ updateHotseatWidthAndBorderSpace(mHotseatColumnSpan + 2);
+ }
+ }
+ if (isQsbInline) {
+ // If QSB is inline, reduce column span until it fits.
+ int maxHotseatWidthAllowedPx = getIconToIconWidthForColumns(numWorkspaceColumns);
+ int minHotseatWidthRequiredPx =
+ mMinHotseatQsbWidthPx + hotseatBorderSpace + mHotseatWidthPx;
+ while (minHotseatWidthRequiredPx > maxHotseatWidthAllowedPx
+ && mHotseatColumnSpan > 1) {
+ updateHotseatWidthAndBorderSpace(mHotseatColumnSpan - 1);
+ minHotseatWidthRequiredPx =
+ mMinHotseatQsbWidthPx + hotseatBorderSpace + mHotseatWidthPx;
+ }
+ }
hotseatQsbWidth = calculateQsbWidth(hotseatBorderSpace);
+
// Spaces should be correct when the nav buttons are not inline
if (!areNavButtonsInline) {
return;
@@ -928,6 +952,12 @@
} while (hotseatBorderSpace < mMinHotseatIconSpacePx && numShownHotseatIcons > 1);
}
+ private void updateHotseatWidthAndBorderSpace(int columns) {
+ mHotseatColumnSpan = columns;
+ mHotseatWidthPx = getIconToIconWidthForColumns(mHotseatColumnSpan);
+ hotseatBorderSpace = calculateHotseatBorderSpace(mHotseatWidthPx, /* numExtraBorder= */ 0);
+ }
+
private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) {
return getCellLayoutBorderSpace(idp, 1f);
}
@@ -951,6 +981,16 @@
return mInfo;
}
+ @VisibleForTesting
+ public int getHotseatColumnSpan() {
+ return mHotseatColumnSpan;
+ }
+
+ @VisibleForTesting
+ public int getHotseatWidthPx() {
+ return mHotseatWidthPx;
+ }
+
public Builder toBuilder(Context context) {
WindowBounds bounds = new WindowBounds(
widthPx, heightPx, availableWidthPx, availableHeightPx, rotationHint);
@@ -2113,7 +2153,8 @@
writer.println(prefix + pxToDpStr("allAppsLeftRightMargin", allAppsLeftRightMargin));
writer.println(prefix + pxToDpStr("hotseatBarSizePx", hotseatBarSizePx));
- writer.println(prefix + "\tinv.hotseatColumnSpan: " + inv.hotseatColumnSpan[mTypeIndex]);
+ writer.println(prefix + "\tmHotseatColumnSpan: " + mHotseatColumnSpan);
+ writer.println(prefix + pxToDpStr("mHotseatWidthPx", mHotseatWidthPx));
writer.println(prefix + pxToDpStr("hotseatCellHeightPx", hotseatCellHeightPx));
writer.println(prefix + pxToDpStr("hotseatBarBottomSpacePx", hotseatBarBottomSpacePx));
writer.println(prefix + pxToDpStr("mHotseatBarEdgePaddingPx",
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 42d4d50..2fd5ebd 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -72,6 +72,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.stream.Collectors;
public class InvariantDeviceProfile {
@@ -162,7 +163,6 @@
*/
public int numDatabaseHotseatIcons;
- public int[] hotseatColumnSpan;
public float[] hotseatBarBottomSpace;
public float[] hotseatQsbSpace;
@@ -358,6 +358,15 @@
return displayOption.grid.name;
}
+ /**
+ * @deprecated This is a temporary solution because on the backup and restore case we modify the
+ * IDP, this resets it. b/332974074
+ */
+ @Deprecated
+ public void reset(Context context) {
+ initGrid(context, getCurrentGridName(context));
+ }
+
@VisibleForTesting
public static String getDefaultGridName(Context context) {
return new InvariantDeviceProfile().initGrid(context, null);
@@ -419,7 +428,6 @@
numShownHotseatIcons = closestProfile.numHotseatIcons;
numDatabaseHotseatIcons = deviceType == TYPE_MULTI_DISPLAY
? closestProfile.numDatabaseHotseatIcons : closestProfile.numHotseatIcons;
- hotseatColumnSpan = closestProfile.hotseatColumnSpan;
hotseatBarBottomSpace = displayOption.hotseatBarBottomSpace;
hotseatQsbSpace = displayOption.hotseatQsbSpace;
@@ -578,6 +586,45 @@
}
/**
+ * Returns the GridOption associated to the given file name or null if the fileName is not
+ * supported.
+ * Ej, launcher.db -> "normal grid", launcher_4_by_4.db -> "practical grid"
+ */
+ public GridOption getGridOptionFromFileName(Context context, String fileName) {
+ return parseAllGridOptions(context).stream()
+ .filter(gridOption -> Objects.equals(gridOption.dbFile, fileName))
+ .findFirst()
+ .orElse(null);
+ }
+
+ /**
+ * Returns the name of the given size on the current device or empty string if the size is not
+ * supported. Ej. 4x4 -> normal, 5x4 -> practical, etc.
+ * (Note: the name of the grid can be different for the same grid size depending of
+ * the values of the InvariantDeviceProfile)
+ *
+ */
+ public String getGridNameFromSize(Context context, Point size) {
+ return parseAllGridOptions(context).stream()
+ .filter(gridOption -> gridOption.numColumns == size.x
+ && gridOption.numRows == size.y)
+ .map(gridOption -> gridOption.name)
+ .findFirst()
+ .orElse("");
+ }
+
+ /**
+ * Returns the grid option for the given gridName on the current device (Note: the gridOption
+ * be different for the same gridName depending on the values of the InvariantDeviceProfile).
+ */
+ public GridOption getGridOptionFromName(Context context, String gridName) {
+ return parseAllGridOptions(context).stream()
+ .filter(gridOption -> Objects.equals(gridOption.name, gridName))
+ .findFirst()
+ .orElse(null);
+ }
+
+ /**
* @return all the grid options that can be shown on the device
*/
public List<GridOption> parseAllGridOptions(Context context) {
@@ -830,8 +877,6 @@
private final int numHotseatIcons;
private final int numDatabaseHotseatIcons;
- private final int[] hotseatColumnSpan = new int[COUNT_SIZES];
-
private final boolean[] inlineQsb = new boolean[COUNT_SIZES];
private @DimenRes int inlineNavButtonsEndSpacing;
@@ -882,17 +927,6 @@
numDatabaseHotseatIcons = a.getInt(
R.styleable.GridDisplayOption_numExtendedHotseatIcons, 2 * numHotseatIcons);
- hotseatColumnSpan[INDEX_DEFAULT] = a.getInt(
- R.styleable.GridDisplayOption_hotseatColumnSpan, numColumns);
- hotseatColumnSpan[INDEX_LANDSCAPE] = a.getInt(
- R.styleable.GridDisplayOption_hotseatColumnSpanLandscape, numColumns);
- hotseatColumnSpan[INDEX_TWO_PANEL_LANDSCAPE] = a.getInt(
- R.styleable.GridDisplayOption_hotseatColumnSpanTwoPanelLandscape,
- numColumns);
- hotseatColumnSpan[INDEX_TWO_PANEL_PORTRAIT] = a.getInt(
- R.styleable.GridDisplayOption_hotseatColumnSpanTwoPanelPortrait,
- numColumns);
-
inlineNavButtonsEndSpacing =
a.getResourceId(R.styleable.GridDisplayOption_inlineNavButtonsEndSpacing,
R.dimen.taskbar_button_margin_default);
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index fba7537..35930c7 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -26,6 +26,7 @@
import androidx.recyclerview.widget.DiffUtil;
import com.android.launcher3.Flags;
+import com.android.launcher3.R;
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -270,6 +271,12 @@
addApps = mWorkProviderManager.shouldShowWorkApps();
}
if (addApps) {
+ if (/* education card was added */ position == 1) {
+ // Add work educard section with "info icon" at 0th position.
+ mFastScrollerSections.add(new FastScrollSectionInfo(
+ mActivityContext.getResources().getString(
+ R.string.work_profile_edu_section), 0));
+ }
position = addAppsWithSections(mApps, position);
}
if (Flags.enablePrivateSpace()) {
diff --git a/src/com/android/launcher3/allapps/WorkEduCard.java b/src/com/android/launcher3/allapps/WorkEduCard.java
index 1059097..5fe2f0c 100644
--- a/src/com/android/launcher3/allapps/WorkEduCard.java
+++ b/src/com/android/launcher3/allapps/WorkEduCard.java
@@ -103,6 +103,8 @@
AllAppsRecyclerView rv = mActivityContext.getAppsView().mAH.get(
ActivityAllAppsContainerView.AdapterHolder.WORK).mRecyclerView;
rv.getApps().getAdapterItems().remove(mPosition);
+ // Remove the educard fast scroll section.
+ rv.getApps().getFastScrollerSections().remove(0);
rv.getAdapter().notifyItemRemoved(mPosition);
}
}
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index f24d1d2..8c68eb8 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -53,6 +53,13 @@
private final @DeviceType int mDeviceType;
private final String mDbFile;
+ public DeviceGridState(int columns, int row, int numHotseat, int deviceType, String dbFile) {
+ mGridSizeString = String.format(Locale.ENGLISH, "%d,%d", columns, row);
+ mNumHotseat = numHotseat;
+ mDeviceType = deviceType;
+ mDbFile = dbFile;
+ }
+
public DeviceGridState(InvariantDeviceProfile idp) {
mGridSizeString = String.format(Locale.ENGLISH, "%d,%d", idp.numColumns, idp.numRows);
mNumHotseat = idp.numDatabaseHotseatIcons;
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index af66431..15190c7 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -38,7 +38,9 @@
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
@@ -94,6 +96,15 @@
return needsToMigrate;
}
+ @VisibleForTesting
+ public static List<DbEntry> readAllEntries(SQLiteDatabase db, String tableName,
+ Context context) {
+ DbReader dbReader = new DbReader(db, tableName, context, getValidPackages(context));
+ List<DbEntry> result = dbReader.loadAllWorkspaceEntries();
+ result.addAll(dbReader.loadHotseatEntries());
+ return result;
+ }
+
/**
* When migrating the grid, we copy the table
* {@link LauncherSettings.Favorites#TABLE_NAME} from {@code source} into
@@ -105,15 +116,23 @@
*/
public static boolean migrateGridIfNeeded(
@NonNull Context context,
- @NonNull InvariantDeviceProfile idp,
+ @NonNull DeviceGridState srcDeviceState,
+ @NonNull DeviceGridState destDeviceState,
@NonNull DatabaseHelper target,
@NonNull SQLiteDatabase source) {
-
- DeviceGridState srcDeviceState = new DeviceGridState(context);
- DeviceGridState destDeviceState = new DeviceGridState(idp);
if (!needsToMigrate(srcDeviceState, destDeviceState)) {
return true;
}
+
+ if (Flags.enableGridMigrationFix()
+ && srcDeviceState.getColumns().equals(destDeviceState.getColumns())
+ && srcDeviceState.getRows() < destDeviceState.getRows()) {
+ // Only use this strategy when comparing the previous grid to the new grid and the
+ // columns are the same and the destination has more rows
+ copyTable(source, TABLE_NAME, target.getWritableDatabase(), TABLE_NAME, context);
+ destDeviceState.writeToPrefs(context);
+ return true;
+ }
copyTable(source, TABLE_NAME, target.getWritableDatabase(), TMP_TABLE, context);
HashSet<String> validPackages = getValidPackages(context);
@@ -656,7 +675,7 @@
}
}
- protected static class DbEntry extends ItemInfo implements Comparable<DbEntry> {
+ public static class DbEntry extends ItemInfo implements Comparable<DbEntry> {
private String mIntent;
private String mProvider;
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index 64771bd..7e1d40d 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -312,8 +312,12 @@
mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
: createDatabaseHelper(true /* forMigration */);
try {
- return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, idp, mOpenHelper,
- oldHelper.getWritableDatabase());
+ // This is the current grid we have, given by the mContext
+ DeviceGridState srcDeviceState = new DeviceGridState(mContext);
+ // This is the state we want to migrate to that is given by the idp
+ DeviceGridState destDeviceState = new DeviceGridState(idp);
+ return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, srcDeviceState,
+ destDeviceState, mOpenHelper, oldHelper.getWritableDatabase());
} catch (Exception e) {
FileLog.e(TAG, "Failed to migrate grid", e);
return false;
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 22bc13b..e6ce337 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -50,8 +50,10 @@
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherFiles;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -73,9 +75,11 @@
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LogConfig;
+import java.io.File;
import java.io.InvalidObjectException;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -121,7 +125,66 @@
// executed again.
LauncherPrefs.get(context).removeSync(RESTORE_DEVICE);
- idp.reinitializeAfterRestore(context);
+ if (Flags.enableNarrowGridRestore()) {
+ String oldPhoneFileName = idp.dbFile;
+ List<String> previousDbs = existingDbs();
+ removeOldDBs(context, oldPhoneFileName);
+ // The idp before this contains data about the old phone, after this it becomes the idp
+ // of the current phone.
+ idp.reset(context);
+ trySettingPreviousGidAsCurrent(context, idp, oldPhoneFileName, previousDbs);
+ } else {
+ idp.reinitializeAfterRestore(context);
+ }
+ }
+
+
+ /**
+ * Try setting the gird used in the previous phone to the new one. If the current device doesn't
+ * support the previous grid option it will not be set.
+ */
+ private static void trySettingPreviousGidAsCurrent(Context context, InvariantDeviceProfile idp,
+ String oldPhoneDbFileName, List<String> previousDbs) {
+ InvariantDeviceProfile.GridOption oldPhoneGridOption = idp.getGridOptionFromFileName(
+ context, oldPhoneDbFileName);
+ // The grid option could be null if current phone doesn't support the previous db.
+ if (oldPhoneGridOption != null) {
+ /* If the user only used the default db on the previous phone and the new default db is
+ * bigger than or equal to the previous one, then keep the new default db */
+ if (previousDbs.size() == 1 && oldPhoneGridOption.numColumns <= idp.numColumns
+ && oldPhoneGridOption.numRows <= idp.numRows) {
+ /* Keep the user in default grid */
+ return;
+ }
+ /*
+ * Here we are setting the previous db as the current one.
+ */
+ idp.setCurrentGrid(context, oldPhoneGridOption.name);
+ }
+ }
+
+ /**
+ * Returns a list of paths of the existing launcher dbs.
+ */
+ private static List<String> existingDbs() {
+ // At this point idp.dbFile contains the name of the dbFile from the previous phone
+ return LauncherFiles.GRID_DB_FILES.stream()
+ .filter(dbName -> new File(dbName).exists())
+ .toList();
+ }
+
+ /**
+ * Only keep the last database used on the previous device.
+ */
+ private static void removeOldDBs(Context context, String oldPhoneDbFileName) {
+ // At this point idp.dbFile contains the name of the dbFile from the previous phone
+ LauncherFiles.GRID_DB_FILES.stream()
+ .filter(dbName -> !dbName.equals(oldPhoneDbFileName))
+ .forEach(dbName -> {
+ if (context.getDatabasePath(dbName).delete()) {
+ FileLog.d(TAG, "Removed old grid db file: " + dbName);
+ }
+ });
}
private static boolean performRestore(Context context, ModelDbController controller) {
diff --git a/tests/assets/databases/BackupAndRestore/launcher.db b/tests/assets/databases/BackupAndRestore/launcher.db
new file mode 100644
index 0000000..126d166
--- /dev/null
+++ b/tests/assets/databases/BackupAndRestore/launcher.db
Binary files differ
diff --git a/tests/assets/databases/BackupAndRestore/launcher_3_by_3.db b/tests/assets/databases/BackupAndRestore/launcher_3_by_3.db
new file mode 100644
index 0000000..6d8cd73
--- /dev/null
+++ b/tests/assets/databases/BackupAndRestore/launcher_3_by_3.db
Binary files differ
diff --git a/tests/assets/databases/BackupAndRestore/launcher_4_by_4.db b/tests/assets/databases/BackupAndRestore/launcher_4_by_4.db
new file mode 100644
index 0000000..00061dd
--- /dev/null
+++ b/tests/assets/databases/BackupAndRestore/launcher_4_by_4.db
Binary files differ
diff --git a/tests/assets/databases/BackupAndRestore/launcher_4_by_5.db b/tests/assets/databases/BackupAndRestore/launcher_4_by_5.db
new file mode 100644
index 0000000..e2e65aa
--- /dev/null
+++ b/tests/assets/databases/BackupAndRestore/launcher_4_by_5.db
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/flagged_result5x5to5x8.db b/tests/assets/databases/GridMigrationTest/flagged_result5x5to5x8.db
new file mode 100644
index 0000000..8bea3ce
--- /dev/null
+++ b/tests/assets/databases/GridMigrationTest/flagged_result5x5to5x8.db
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/result5x5to3x3.db b/tests/assets/databases/GridMigrationTest/result5x5to3x3.db
new file mode 100644
index 0000000..686056d
--- /dev/null
+++ b/tests/assets/databases/GridMigrationTest/result5x5to3x3.db
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/result5x5to4x7.db b/tests/assets/databases/GridMigrationTest/result5x5to4x7.db
new file mode 100644
index 0000000..cd105c5
--- /dev/null
+++ b/tests/assets/databases/GridMigrationTest/result5x5to4x7.db
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/result5x5to5x8.db b/tests/assets/databases/GridMigrationTest/result5x5to5x8.db
new file mode 100644
index 0000000..4b46969
--- /dev/null
+++ b/tests/assets/databases/GridMigrationTest/result5x5to5x8.db
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/test_launcher.db b/tests/assets/databases/GridMigrationTest/test_launcher.db
new file mode 100644
index 0000000..c680e95
--- /dev/null
+++ b/tests/assets/databases/GridMigrationTest/test_launcher.db
Binary files differ
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
index af8f67f..eb09975 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 0.0px (0.0dp)
allAppsLeftRightMargin: 0.0px (0.0dp)
hotseatBarSizePx: 273.0px (104.0dp)
- inv.hotseatColumnSpan: 5
+ mHotseatColumnSpan: 5
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 166.0px (63.238094dp)
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
index 5b83dd7..fded562 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 0.0px (0.0dp)
allAppsLeftRightMargin: 0.0px (0.0dp)
hotseatBarSizePx: 294.0px (112.0dp)
- inv.hotseatColumnSpan: 5
+ mHotseatColumnSpan: 5
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 166.0px (63.238094dp)
hotseatBarBottomSpacePx: 147.0px (56.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
index 03a0048..406fa43 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 0.0px (0.0dp)
allAppsLeftRightMargin: 0.0px (0.0dp)
hotseatBarSizePx: 252.0px (96.0dp)
- inv.hotseatColumnSpan: 5
+ mHotseatColumnSpan: 5
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 166.0px (63.238094dp)
hotseatBarBottomSpacePx: 0.0px (0.0dp)
mHotseatBarEdgePaddingPx: 63.0px (24.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
index 84258b3..735b54a 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 0.0px (0.0dp)
allAppsLeftRightMargin: 0.0px (0.0dp)
hotseatBarSizePx: 252.0px (96.0dp)
- inv.hotseatColumnSpan: 5
+ mHotseatColumnSpan: 5
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 166.0px (63.238094dp)
hotseatBarBottomSpacePx: 0.0px (0.0dp)
mHotseatBarEdgePaddingPx: 63.0px (24.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
index 87a9700..dc8d47e 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 32.0px (16.0dp)
allAppsLeftRightMargin: 412.0px (206.0dp)
hotseatBarSizePx: 200.0px (100.0dp)
- inv.hotseatColumnSpan: 4
+ mHotseatColumnSpan: 6
+ mHotseatWidthPx: 1960.0px (980.0dp)
hotseatCellHeightPx: 135.0px (67.5dp)
hotseatBarBottomSpacePx: 80.0px (40.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
@@ -82,12 +83,12 @@
springLoadedHotseatBarTopMarginPx: 128.0px (64.0dp)
getHotseatLayoutPadding(context).top: 0.0px (0.0dp)
getHotseatLayoutPadding(context).bottom: 65.0px (32.5dp)
- getHotseatLayoutPadding(context).left: 668.0px (334.0dp)
- getHotseatLayoutPadding(context).right: 668.0px (334.0dp)
+ getHotseatLayoutPadding(context).left: 300.0px (150.0dp)
+ getHotseatLayoutPadding(context).right: 300.0px (150.0dp)
numShownHotseatIcons: 6
- hotseatBorderSpace: 100.0px (50.0dp)
+ hotseatBorderSpace: 248.0px (124.0dp)
isQsbInline: false
- hotseatQsbWidth: 1214.0px (607.0dp)
+ hotseatQsbWidth: 1950.0px (975.0dp)
isTaskbarPresent:false
isTaskbarPresentInApps:true
taskbarHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
index 169256d..611da6c 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 32.0px (16.0dp)
allAppsLeftRightMargin: 412.0px (206.0dp)
hotseatBarSizePx: 200.0px (100.0dp)
- inv.hotseatColumnSpan: 4
+ mHotseatColumnSpan: 6
+ mHotseatWidthPx: 1960.0px (980.0dp)
hotseatCellHeightPx: 135.0px (67.5dp)
hotseatBarBottomSpacePx: 80.0px (40.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
@@ -82,12 +83,12 @@
springLoadedHotseatBarTopMarginPx: 128.0px (64.0dp)
getHotseatLayoutPadding(context).top: 0.0px (0.0dp)
getHotseatLayoutPadding(context).bottom: 65.0px (32.5dp)
- getHotseatLayoutPadding(context).left: 668.0px (334.0dp)
- getHotseatLayoutPadding(context).right: 668.0px (334.0dp)
+ getHotseatLayoutPadding(context).left: 300.0px (150.0dp)
+ getHotseatLayoutPadding(context).right: 300.0px (150.0dp)
numShownHotseatIcons: 6
- hotseatBorderSpace: 100.0px (50.0dp)
+ hotseatBorderSpace: 248.0px (124.0dp)
isQsbInline: false
- hotseatQsbWidth: 1214.0px (607.0dp)
+ hotseatQsbWidth: 1950.0px (975.0dp)
isTaskbarPresent:false
isTaskbarPresentInApps:true
taskbarHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
index 59da1be..1354be4 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 32.0px (16.0dp)
allAppsLeftRightMargin: 152.0px (76.0dp)
hotseatBarSizePx: 272.0px (136.0dp)
- inv.hotseatColumnSpan: 6
+ mHotseatColumnSpan: 6
+ mHotseatWidthPx: 1300.0px (650.0dp)
hotseatCellHeightPx: 135.0px (67.5dp)
hotseatBarBottomSpacePx: 152.0px (76.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
index ad1f095..b448160 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 32.0px (16.0dp)
allAppsLeftRightMargin: 152.0px (76.0dp)
hotseatBarSizePx: 272.0px (136.0dp)
- inv.hotseatColumnSpan: 6
+ mHotseatColumnSpan: 6
+ mHotseatWidthPx: 1300.0px (650.0dp)
hotseatCellHeightPx: 135.0px (67.5dp)
hotseatBarBottomSpacePx: 152.0px (76.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
index 61d5ee6..f111c8d 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 42.0px (16.0dp)
allAppsLeftRightMargin: 183.0px (69.71429dp)
hotseatBarSizePx: 267.0px (101.71429dp)
- inv.hotseatColumnSpan: 4
+ mHotseatColumnSpan: 4
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 159.0px (60.57143dp)
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
index ac73e2f..4ccb4f3 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 42.0px (16.0dp)
allAppsLeftRightMargin: 183.0px (69.71429dp)
hotseatBarSizePx: 267.0px (101.71429dp)
- inv.hotseatColumnSpan: 4
+ mHotseatColumnSpan: 4
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 159.0px (60.57143dp)
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
index 3169f41..87798da 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 42.0px (16.0dp)
allAppsLeftRightMargin: 1.0px (0.3809524dp)
hotseatBarSizePx: 267.0px (101.71429dp)
- inv.hotseatColumnSpan: 4
+ mHotseatColumnSpan: 4
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 159.0px (60.57143dp)
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
index 9d3d7bc..ae52f06 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
@@ -71,7 +71,8 @@
allAppsPadding.right: 42.0px (16.0dp)
allAppsLeftRightMargin: 1.0px (0.3809524dp)
hotseatBarSizePx: 267.0px (101.71429dp)
- inv.hotseatColumnSpan: 4
+ mHotseatColumnSpan: 4
+ mHotseatWidthPx: 0.0px (0.0dp)
hotseatCellHeightPx: 159.0px (60.57143dp)
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/rule/BackAndRestoreRule.kt b/tests/multivalentTests/src/com/android/launcher3/util/rule/BackAndRestoreRule.kt
new file mode 100644
index 0000000..9ac976f
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/rule/BackAndRestoreRule.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 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.util.rule
+
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherPrefs
+import java.io.File
+import java.nio.file.Paths
+import kotlin.io.path.pathString
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Removes all launcher's DBs from the device and copies the dbs in
+ * assets/databases/BackupAndRestore to the device. It also set's the needed LauncherPrefs variables
+ * needed to kickstart a backup and restore.
+ */
+class BackAndRestoreRule : TestRule {
+
+ private val phoneContext = getInstrumentation().targetContext
+
+ private fun dbBackUp() = File(phoneContext.dataDir.path, "/databasesBackUp")
+
+ private fun dbDirectory() = File(phoneContext.dataDir.path, "/databases")
+
+ private fun isWorkspaceDatabase(rawFileName: String): Boolean {
+ val fileName = Paths.get(rawFileName).fileName.pathString
+ return fileName.startsWith("launcher") && fileName.endsWith(".db")
+ }
+
+ fun getDatabaseFiles() = dbDirectory().listFiles().filter { isWorkspaceDatabase(it.name) }
+
+ /**
+ * Setting RESTORE_DEVICE would trigger a restore next time the Launcher starts, and we remove
+ * the widgets and apps ids to prevent issues when loading the database.
+ */
+ private fun setRestoreConstants() {
+ LauncherPrefs.get(phoneContext)
+ .put(LauncherPrefs.RESTORE_DEVICE.to(InvariantDeviceProfile.TYPE_MULTI_DISPLAY))
+ LauncherPrefs.get(phoneContext)
+ .remove(LauncherPrefs.OLD_APP_WIDGET_IDS, LauncherPrefs.APP_WIDGET_IDS)
+ }
+
+ private fun uploadDatabase(dbName: String) {
+ val file = File(File(getInstrumentation().targetContext.dataDir, "/databases"), dbName)
+ file.writeBytes(
+ getInstrumentation()
+ .context
+ .assets
+ .open("databases/BackupAndRestore/$dbName")
+ .readBytes()
+ )
+ file.setWritable(true, false)
+ }
+
+ private fun uploadDbs() {
+ uploadDatabase("launcher.db")
+ uploadDatabase("launcher_4_by_4.db")
+ uploadDatabase("launcher_4_by_5.db")
+ uploadDatabase("launcher_3_by_3.db")
+ }
+
+ private fun savePreviousState() {
+ dbBackUp().deleteRecursively()
+ if (!dbDirectory().renameTo(dbBackUp())) {
+ throw Exception("Unable to move databases to backup directory")
+ }
+ dbDirectory().mkdir()
+ if (!dbDirectory().exists()) {
+ throw Exception("Databases directory doesn't exist")
+ }
+ }
+
+ private fun restorePreviousState() {
+ dbDirectory().deleteRecursively()
+ if (!dbBackUp().renameTo(dbDirectory())) {
+ throw Exception("Unable to restore backup directory to databases directory")
+ }
+ dbBackUp().delete()
+ }
+
+ fun before() {
+ savePreviousState()
+ setRestoreConstants()
+ uploadDbs()
+ }
+
+ fun after() {
+ restorePreviousState()
+ }
+
+ override fun apply(base: Statement?, description: Description?): Statement =
+ object : Statement() {
+ override fun evaluate() {
+ before()
+ try {
+ base?.evaluate()
+ } finally {
+ after()
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index 251a401..13d7499 100644
--- a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -157,7 +157,6 @@
numDatabaseHotseatIcons = 4
- hotseatColumnSpan = IntArray(4) { 4 }
hotseatBarBottomSpace = FloatArray(4) { 48f }
hotseatQsbSpace = FloatArray(4) { 36f }
@@ -240,7 +239,6 @@
numDatabaseHotseatIcons = 6
- hotseatColumnSpan = intArrayOf(6, 4, 6, 6)
hotseatBarBottomSpace = floatArrayOf(36f, 40f, 36f, 36f)
hotseatQsbSpace = FloatArray(4) { 32f }
@@ -259,8 +257,10 @@
}
protected fun initializeVarsForTwoPanel(
- isLandscape: Boolean = false,
- isGestureMode: Boolean = true
+ isLandscape: Boolean = false,
+ isGestureMode: Boolean = true,
+ rows: Int = 4,
+ cols: Int = 4,
) {
val (x, y) = if (isLandscape) Pair(2208, 1840) else Pair(1840, 2208)
@@ -276,9 +276,9 @@
inv =
InvariantDeviceProfile().apply {
- numRows = 4
- numColumns = 4
- numSearchContainerColumns = 4
+ numRows = rows
+ numColumns = cols
+ numSearchContainerColumns = cols
iconSize = floatArrayOf(60f, 52f, 52f, 60f)
iconTextSize = floatArrayOf(14f, 14f, 12f, 14f)
@@ -319,7 +319,6 @@
numDatabaseHotseatIcons = 6
- hotseatColumnSpan = IntArray(4) { 6 }
hotseatBarBottomSpace = floatArrayOf(48f, 48f, 36f, 20f)
hotseatQsbSpace = floatArrayOf(36f, 36f, 36f, 28f)
diff --git a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
new file mode 100644
index 0000000..479b201
--- /dev/null
+++ b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 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.backuprestore
+
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.Flags
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.model.ModelDbController
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.TestUtil
+import com.android.launcher3.util.rule.BackAndRestoreRule
+import com.android.launcher3.util.rule.setFlags
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Makes sure to test {@code RestoreDbTask#removeOldDBs}, we need to remove all the dbs that are not
+ * the last one used when we restore the device.
+ */
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class BackupAndRestoreDBSelectionTest {
+
+ @JvmField @Rule var backAndRestoreRule = BackAndRestoreRule()
+
+ @JvmField
+ @Rule
+ val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+
+ @Before
+ fun setUp() {
+ setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_NARROW_GRID_RESTORE)
+ }
+
+ @Test
+ fun oldDatabasesNotPresentAfterRestore() {
+ val dbController = ModelDbController(getInstrumentation().targetContext)
+ dbController.tryMigrateDB(null)
+ TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
+ assert(backAndRestoreRule.getDatabaseFiles().size == 1) {
+ "There should only be one database after restoring, the last one used. Actual databases ${backAndRestoreRule.getDatabaseFiles()}"
+ }
+ assert(
+ !LauncherPrefs.get(getInstrumentation().targetContext)
+ .has(LauncherPrefs.RESTORE_DEVICE)
+ ) {
+ "RESTORE_DEVICE shouldn't be present after a backup and restore."
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java b/tests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
index 62f2259..e5ad888 100644
--- a/tests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
+++ b/tests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
@@ -199,6 +199,19 @@
return 'z';
}
+ /**
+ * Check if the given area is empty.
+ */
+ public boolean isEmpty(int x, int y, int spanX, int spanY) {
+ for (int xi = x; xi < x + spanX; xi++) {
+ for (int yi = y; yi < y + spanY; yi++) {
+ if (mWidget[xi][yi] == CellType.IGNORE) continue;
+ if (mWidget[xi][yi] != CellType.EMPTY) return false;
+ }
+ }
+ return true;
+ }
+
public void addWidget(int x, int y, int spanX, int spanY, char type) {
Rect rect = new Rect(x, y + spanY - 1, x + spanX - 1, y);
removeOverlappingItems(rect);
diff --git a/tests/src/com/android/launcher3/model/GridMigrationTest.kt b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
new file mode 100644
index 0000000..15222a4
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2024 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.model
+
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.Flags
+import com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE
+import com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME
+import com.android.launcher3.celllayout.board.CellLayoutBoard
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.rule.TestToPhoneFileCopier
+import com.android.launcher3.util.rule.setFlags
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private val phoneContext = InstrumentationRegistry.getInstrumentation().targetContext
+
+data class EntryData(val x: Int, val y: Int, val spanX: Int, val spanY: Int, val rank: Int)
+
+/**
+ * Holds the data needed to run a test in GridMigrationTest, usually we would have a src
+ * GridMigrationData and a dst GridMigrationData meaning the data after a migration has occurred.
+ * This class holds a gridState, which is the size of the grid like 5x5 (among other things). a
+ * dbHelper which contains the readable database and writable database used to migrate the
+ * databases.
+ *
+ * You can also get all the entries defined in the dbHelper database.
+ */
+class GridMigrationData(dbFileName: String?, val gridState: DeviceGridState) {
+
+ val dbHelper: DatabaseHelper =
+ DatabaseHelper(
+ phoneContext,
+ dbFileName,
+ { UserCache.INSTANCE.get(phoneContext).getSerialNumberForUser(it) },
+ {}
+ )
+
+ fun readEntries(): List<GridSizeMigrationUtil.DbEntry> =
+ GridSizeMigrationUtil.readAllEntries(dbHelper.readableDatabase, TABLE_NAME, phoneContext)
+}
+
+/**
+ * Test the migration of a database from one size to another. It reads a database from the test
+ * assets, uploads it into the phone and migrates the database to a database in memory which is
+ * later compared against a database in the test assets to make sure they are identical.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GridMigrationTest {
+ private val DB_FILE = "test_launcher.db"
+
+ @JvmField
+ @Rule
+ val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+
+ // Copying the src db for all tests.
+ @JvmField
+ @Rule
+ val fileCopier =
+ TestToPhoneFileCopier(
+ src = "databases/GridMigrationTest/$DB_FILE",
+ dest = "databases/$DB_FILE",
+ removeOnFinish = true
+ )
+
+ @Before
+ fun setup() {
+ setFlagsRule.setFlags(false, Flags.FLAG_ENABLE_GRID_MIGRATION_FIX)
+ }
+
+ private fun migrate(src: GridMigrationData, dst: GridMigrationData) {
+ GridSizeMigrationUtil.migrateGridIfNeeded(
+ phoneContext,
+ src.gridState,
+ dst.gridState,
+ dst.dbHelper,
+ src.dbHelper.readableDatabase
+ )
+ }
+
+ /**
+ * Makes sure that none of the items overlaps on the result, i.e. no widget or icons share the
+ * same space in the db.
+ */
+ private fun validateDb(data: GridMigrationData) {
+ // The array size is just a big enough number to fit all the number of workspaces
+ val boards = Array(100) { CellLayoutBoard(data.gridState.columns, data.gridState.rows) }
+ data.readEntries().forEach {
+ val cellLayoutBoard = boards[it.screenId]
+ assert(cellLayoutBoard.isEmpty(it.cellX, it.cellY, it.spanX, it.spanY)) {
+ "Db has overlapping items"
+ }
+ cellLayoutBoard.addWidget(it.cellX, it.cellY, it.spanX, it.spanY)
+ }
+ }
+
+ private fun compare(dst: GridMigrationData, target: GridMigrationData) {
+ val sort = compareBy<GridSizeMigrationUtil.DbEntry>({ it.cellX }, { it.cellY })
+ val mapF = { it: GridSizeMigrationUtil.DbEntry ->
+ EntryData(it.cellX, it.cellY, it.spanX, it.spanY, it.rank)
+ }
+ val entriesDst = dst.readEntries().sortedWith(sort).map(mapF)
+ val entriesTarget = target.readEntries().sortedWith(sort).map(mapF)
+
+ assert(entriesDst == entriesTarget) {
+ "The elements on the dst database is not the same as in the target"
+ }
+ }
+
+ /**
+ * Migrate src into dst and compare to target. This method validates 3 things:
+ * 1. dst has the same number of items as src after the migration, meaning, none of the items
+ * were removed during the migration.
+ * 2. dst is valid, meaning that none of the items overlap with each other.
+ * 3. dst is equal to target to ensure we don't unintentionally change the migration logic.
+ */
+ private fun runTest(src: GridMigrationData, dst: GridMigrationData, target: GridMigrationData) {
+ migrate(src, dst)
+ assert(src.readEntries().size == dst.readEntries().size) {
+ "Source db and destination db do not contain the same number of elements"
+ }
+ validateDb(dst)
+ compare(dst, target)
+ }
+
+ @JvmField
+ @Rule
+ val result5x5to3x3 =
+ TestToPhoneFileCopier(
+ src = "databases/GridMigrationTest/result5x5to3x3.db",
+ dest = "databases/result5x5to3x3.db",
+ removeOnFinish = true
+ )
+
+ @Test
+ fun `5x5 to 3x3`() =
+ runTest(
+ src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)),
+ dst =
+ GridMigrationData(
+ null, // in memory db, to download a new db change null for the filename of the
+ // db name to store it. Do not use existing names.
+ DeviceGridState(3, 3, 3, TYPE_PHONE, "")
+ ),
+ target =
+ GridMigrationData("result5x5to3x3.db", DeviceGridState(3, 3, 3, TYPE_PHONE, ""))
+ )
+
+ @JvmField
+ @Rule
+ val result5x5to4x7 =
+ TestToPhoneFileCopier(
+ src = "databases/GridMigrationTest/result5x5to4x7.db",
+ dest = "databases/result5x5to4x7.db",
+ removeOnFinish = true
+ )
+
+ @Test
+ fun `5x5 to 4x7`() =
+ runTest(
+ src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)),
+ dst =
+ GridMigrationData(
+ null, // in memory db, to download a new db change null for the filename of the
+ // db name to store it. Do not use existing names.
+ DeviceGridState(4, 7, 4, TYPE_PHONE, "")
+ ),
+ target =
+ GridMigrationData("result5x5to4x7.db", DeviceGridState(4, 7, 4, TYPE_PHONE, ""))
+ )
+
+ @JvmField
+ @Rule
+ val result5x5to5x8 =
+ TestToPhoneFileCopier(
+ src = "databases/GridMigrationTest/result5x5to5x8.db",
+ dest = "databases/result5x5to5x8.db",
+ removeOnFinish = true
+ )
+
+ @Test
+ fun `5x5 to 5x8`() =
+ runTest(
+ src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)),
+ dst =
+ GridMigrationData(
+ null, // in memory db, to download a new db change null for the filename of the
+ // db name to store it. Do not use existing names.
+ DeviceGridState(5, 8, 5, TYPE_PHONE, "")
+ ),
+ target =
+ GridMigrationData("result5x5to5x8.db", DeviceGridState(5, 8, 5, TYPE_PHONE, ""))
+ )
+
+ @JvmField
+ @Rule
+ val flaggedResult5x5to5x8 =
+ TestToPhoneFileCopier(
+ src = "databases/GridMigrationTest/flagged_result5x5to5x8.db",
+ dest = "databases/flagged_result5x5to5x8.db",
+ removeOnFinish = true
+ )
+
+ @Test
+ fun `flagged 5x5 to 5x8`() {
+ setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_GRID_MIGRATION_FIX)
+ runTest(
+ src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)),
+ dst =
+ GridMigrationData(
+ null, // in memory db, to download a new db change null for the filename of the
+ // db name to store it. Do not use existing names.
+ DeviceGridState(5, 8, 5, TYPE_PHONE, "")
+ ),
+ target =
+ GridMigrationData(
+ "flagged_result5x5to5x8.db",
+ DeviceGridState(5, 8, 5, TYPE_PHONE, "")
+ )
+ )
+ }
+}
diff --git a/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt b/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
index 9912a34..408691c 100644
--- a/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
@@ -42,6 +42,8 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
assertThat(dp.hotseatBorderSpace).isEqualTo(145)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1445)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(177)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(177)
@@ -64,6 +66,8 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
assertThat(dp.hotseatBorderSpace).isEqualTo(72)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1080)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(110)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(110)
@@ -85,6 +89,8 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
assertThat(dp.hotseatBorderSpace).isEqualTo(104)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1468)
assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(370)
assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(370)
@@ -100,22 +106,21 @@
@Test
fun nav_buttons_dont_interfere_with_required_hotseat_width() {
initializeVarsForTablet(isGestureMode = false, isLandscape = true)
- inv?.apply {
- hotseatColumnSpan = IntArray(4) { 4 }
- inlineQsb = BooleanArray(4) { false }
- }
+ inv?.apply { inlineQsb = BooleanArray(4) { false } }
val dp = newDP()
dp.isTaskbarPresentInApps = true
assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
- assertThat(dp.hotseatBorderSpace).isEqualTo(100)
+ assertThat(dp.hotseatBorderSpace).isEqualTo(248)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1960)
- assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(668)
- assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(668)
+ assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(300)
+ assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(300)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1214)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1950)
}
/** This is a case when after setting the hotseat, the QSB width needs to be changed to fit */
@@ -128,13 +133,15 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
- assertThat(dp.hotseatBorderSpace).isEqualTo(91)
+ assertThat(dp.hotseatBorderSpace).isEqualTo(233)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1885)
- assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(640)
- assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(640)
+ assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(287)
+ assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(287)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1169)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1875)
}
/**
@@ -150,13 +157,15 @@
assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
assertThat(dp.numShownHotseatIcons).isEqualTo(6)
- assertThat(dp.hotseatBorderSpace).isEqualTo(75)
+ assertThat(dp.hotseatBorderSpace).isEqualTo(205)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(6)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1745)
- assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(582)
- assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(582)
+ assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(257)
+ assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(257)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.hotseatQsbWidth).isEqualTo(1085)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1735)
}
@Test
@@ -179,4 +188,23 @@
assertThat(dp.hotseatQsbWidth).isEqualTo(1435)
}
}
+
+ @Test
+ fun increase_span_when_space_between_icons_is_less_than_minimum() {
+ initializeVarsForTwoPanel(isGestureMode = false, isLandscape = false, rows = 5, cols = 5)
+ val dp = newDP()
+ dp.isTaskbarPresentInApps = true
+
+ assertThat(dp.hotseatBarEndOffset).isEqualTo(0)
+ assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+ assertThat(dp.hotseatBorderSpace).isEqualTo(112)
+ assertThat(dp.hotseatColumnSpan).isEqualTo(8)
+ assertThat(dp.hotseatWidthPx).isEqualTo(1383)
+
+ assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(228)
+ assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(228)
+
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1372)
+ }
}
diff --git a/tests/src/com/android/launcher3/util/rule/TestToPhoneFileCopier.kt b/tests/src/com/android/launcher3/util/rule/TestToPhoneFileCopier.kt
new file mode 100644
index 0000000..d3516d1
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/TestToPhoneFileCopier.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 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.util.rule
+
+import androidx.test.platform.app.InstrumentationRegistry
+import java.io.File
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/** Copy a file from the tests assets folder to the phone. */
+class TestToPhoneFileCopier(
+ val src: String,
+ dest: String,
+ private val removeOnFinish: Boolean = false
+) : TestRule {
+
+ private val dstFile =
+ File(InstrumentationRegistry.getInstrumentation().targetContext.dataDir, dest)
+
+ fun getDst() = dstFile.absolutePath
+
+ fun before() =
+ dstFile.writeBytes(
+ InstrumentationRegistry.getInstrumentation().context.assets.open(src).readBytes()
+ )
+
+ fun after() {
+ if (removeOnFinish) {
+ dstFile.delete()
+ }
+ }
+
+ override fun apply(base: Statement, description: Description): Statement =
+ object : Statement() {
+ override fun evaluate() {
+ before()
+ try {
+ base.evaluate()
+ } finally {
+ after()
+ }
+ }
+ }
+}