diff --git a/flexera/res/layout/all_apps_icon.xml b/flexera/res/layout/all_apps_icon.xml
new file mode 100644
index 0000000000..35e1338262
--- /dev/null
+++ b/flexera/res/layout/all_apps_icon.xml
@@ -0,0 +1,21 @@
+
+
+
diff --git a/flexera/res/layout/flexera_all_apps_section_header.xml b/flexera/res/layout/flexera_all_apps_section_header.xml
new file mode 100644
index 0000000000..4fe50bbf59
--- /dev/null
+++ b/flexera/res/layout/flexera_all_apps_section_header.xml
@@ -0,0 +1,15 @@
+
+
+
diff --git a/flexera/res/values/flexera_config.xml b/flexera/res/values/flexera_config.xml
index 0e872da73f..29f6bb8dfa 100644
--- a/flexera/res/values/flexera_config.xml
+++ b/flexera/res/values/flexera_config.xml
@@ -14,4 +14,7 @@
false
+
+
+ false
diff --git a/flexera/res/values/flexera_dimens.xml b/flexera/res/values/flexera_dimens.xml
new file mode 100644
index 0000000000..ef0d5370aa
--- /dev/null
+++ b/flexera/res/values/flexera_dimens.xml
@@ -0,0 +1,16 @@
+
+
+
+ 64dp
+
+ 46dp
+ 20dp
+ 17sp
+ 24dp
+
+ 56dp
+
+ 44dp
+ 6dp
+ 14sp
+
diff --git a/flexera/src/app/flexera/drawer/FlexeraDrawer.kt b/flexera/src/app/flexera/drawer/FlexeraDrawer.kt
new file mode 100644
index 0000000000..4258cfaf13
--- /dev/null
+++ b/flexera/src/app/flexera/drawer/FlexeraDrawer.kt
@@ -0,0 +1,77 @@
+package app.flexera.drawer
+
+import android.content.Context
+import android.util.TypedValue
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.R
+import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem
+
+/**
+ * Flexera Launcher's app drawer: a single-column alphabetical list with letter headers
+ * (PRD AD-2 and AD-3), built on Launcher3's all-apps adapter rather than replacing it.
+ *
+ * Launcher3 keeps doing the work it already does well: loading apps, sorting them, live updates,
+ * click, long-press menu and drag. This object supplies only what differs from the stock grid:
+ * - the column count ([COLUMNS]),
+ * - the look of a row ([styleAppRow]; the row layout itself is flexera/res/layout/all_apps_icon.xml,
+ * which overrides Launcher3's layout of the same name), and
+ * - a letter header row ([VIEW_TYPE_SECTION_HEADER] and the functions that create and bind it).
+ */
+object FlexeraDrawer {
+
+ /** The drawer is one column wide. */
+ const val COLUMNS = 1
+
+ /**
+ * Adapter view type of a letter header row. Launcher3 uses bits 1 to 10 and Lawnchair's search
+ * results use bits 10 to 22, so this stays clear of both.
+ */
+ const val VIEW_TYPE_SECTION_HEADER = 1 shl 24
+
+ /** Creates the adapter item for the header that starts the section named [sectionName]. */
+ @JvmStatic
+ fun sectionHeader(sectionName: String): AdapterItem = SectionHeaderItem(sectionName)
+
+ @JvmStatic
+ fun createSectionHeader(inflater: LayoutInflater, parent: ViewGroup): View =
+ inflater.inflate(R.layout.flexera_all_apps_section_header, parent, false)
+
+ @JvmStatic
+ fun bindSectionHeader(view: View, item: AdapterItem) {
+ (view as TextView).text = (item as? SectionHeaderItem)?.sectionName.orEmpty()
+ }
+
+ /**
+ * Finishes turning a freshly inflated all-apps icon into a list row. Launcher3 sizes the view
+ * as a grid cell (icon above a small label); a row needs its own height, a wider gap between
+ * icon and name, and a label large enough to read in a list.
+ */
+ @JvmStatic
+ fun styleAppRow(icon: BubbleTextView) {
+ val res = icon.resources
+ icon.layoutParams.height = res.getDimensionPixelSize(R.dimen.flexera_drawer_row_height)
+ icon.compoundDrawablePadding = res.getDimensionPixelSize(R.dimen.flexera_drawer_row_icon_gap)
+ icon.setTextSize(TypedValue.COMPLEX_UNIT_PX, res.getDimension(R.dimen.flexera_drawer_row_text_size))
+ }
+
+ /**
+ * How far a row's icon sits from the edge of the list. Launcher3 uses its grid version of
+ * this number to line the search bar up with the first icon, so the list supplies its own.
+ */
+ @JvmStatic
+ fun rowStartPadding(context: Context): Int =
+ context.resources.getDimensionPixelSize(R.dimen.flexera_drawer_row_padding_start)
+
+ /**
+ * A letter header. It carries its letter so that the adapter's diffing notices when the
+ * header at a position changes, for example after an app is installed or removed.
+ */
+ class SectionHeaderItem(val sectionName: String) : AdapterItem(VIEW_TYPE_SECTION_HEADER) {
+ override fun isContentSame(other: AdapterItem): Boolean =
+ other is SectionHeaderItem && other.sectionName == sectionName
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/DeviceProfileOverrides.kt b/lawnchair/src/app/lawnchair/DeviceProfileOverrides.kt
index 01b9ad6d36..c20c3671af 100644
--- a/lawnchair/src/app/lawnchair/DeviceProfileOverrides.kt
+++ b/lawnchair/src/app/lawnchair/DeviceProfileOverrides.kt
@@ -158,8 +158,9 @@ class DeviceProfileOverrides @Inject constructor(
fun applyUi(idp: InvariantDeviceProfile) {
// apply grid size
- idp.numAllAppsColumns = numAllAppsColumns
- idp.numDatabaseAllAppsColumns = numAllAppsColumns
+ // Flexera Launcher: the drawer is a one-column list, whatever the column preference says.
+ idp.numAllAppsColumns = app.flexera.drawer.FlexeraDrawer.COLUMNS
+ idp.numDatabaseAllAppsColumns = app.flexera.drawer.FlexeraDrawer.COLUMNS
idp.numFolderRows[INDEX_DEFAULT] = numFolderRows
idp.numFolderColumns[INDEX_DEFAULT] = numFolderColumns
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 4e22812b41..2bf7f0ce2f 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -1979,6 +1979,14 @@ public class DeviceProfile {
/** The margin between the edge of all apps and the edge of the first icon. */
public int getAllAppsIconStartMargin(Context context) {
+ // Flexera Launcher: in the one-column drawer list the first icon sits at the row's start
+ // padding. The grid maths below would instead centre it in a full-width cell, which
+ // squeezed the search bar down to a small pill.
+ if (numShownAllAppsColumns == app.flexera.drawer.FlexeraDrawer.COLUMNS) {
+ return (Utilities.isRtl(context.getResources()) ? allAppsPadding.right
+ : allAppsPadding.left)
+ + app.flexera.drawer.FlexeraDrawer.rowStartPadding(context);
+ }
int allAppsSpacing;
if (isVerticalBarLayout()) {
// On phones, the landscape layout uses a different setup.
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 7dadc50a76..7301fedb73 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -491,6 +491,17 @@ public class AlphabeticalAppsList implement
Log.d(TAG, "Adding apps with sections. HasPrivateApps: " + hasPrivateApps);
for (int i = 0; i < appList.size(); i++) {
AppInfo info = appList.get(i);
+ // Flexera Launcher: start each letter group with a header row (PRD AD-3). The header
+ // takes this position, so the fast-scroll section created below lands on the header.
+ // Private Space keeps Launcher3's own framing and gets no headers.
+ if (!hasPrivateApps && !info.sectionName.equals(lastSectionName)) {
+ mAdapterItems.add(app.flexera.drawer.FlexeraDrawer.sectionHeader(info.sectionName));
+ Log.d(TAG, "addAppsWithSections: adding sectionName: " + info.sectionName
+ + " with header at position: " + position);
+ lastSectionName = info.sectionName;
+ mFastScrollerSections.add(new FastScrollSectionInfo(info.sectionName, position));
+ position++;
+ }
// Apply decorator to private apps.
if (hasPrivateApps) {
mAdapterItems.add(AdapterItem.asAppWithDecorationInfo(info,
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 6f995b5ae3..e06e668151 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -243,7 +243,13 @@ public abstract class BaseAllAppsAdapter ex
// Ensure the all apps icon height matches the workspace icons in portrait mode.
icon.getLayoutParams().height =
mActivityContext.getDeviceProfile().getAllAppsProfile().getCellHeightPx();
+ // Flexera Launcher: the drawer is a list, so size and style this as a row.
+ app.flexera.drawer.FlexeraDrawer.styleAppRow(icon);
return new ViewHolder(icon);
+ case app.flexera.drawer.FlexeraDrawer.VIEW_TYPE_SECTION_HEADER:
+ // Flexera Launcher: letter header that starts each group in the drawer list.
+ return new ViewHolder(app.flexera.drawer.FlexeraDrawer.createSectionHeader(
+ mLayoutInflater, parent));
case VIEW_TYPE_EMPTY_SEARCH:
return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
parent, false));
@@ -325,6 +331,11 @@ public abstract class BaseAllAppsAdapter ex
}
break;
}
+ case app.flexera.drawer.FlexeraDrawer.VIEW_TYPE_SECTION_HEADER:
+ // Flexera Launcher: letter header in the drawer list.
+ app.flexera.drawer.FlexeraDrawer.bindSectionHeader(
+ holder.itemView, mApps.getAdapterItems().get(position));
+ break;
case VIEW_TYPE_EMPTY_SEARCH: {
AppInfo info = mApps.getAdapterItems().get(position).itemInfo;
if (info != null) {