1 parent
142cbea797
commit
99a4e77713
21 files changed
+783
-188
No files matched your search
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2026 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.
|
||||||
|
|
||||||
|
Modifications copyright 2026 Lawnchair
|
||||||
|
-->
|
||||||
|
<com.android.launcher3.CellLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:hapticFeedbackEnabled="false"
|
||||||
|
launcher:containerType="hotseat" />
|
||||||
@@ -782,6 +782,7 @@
|
|||||||
|
|
||||||
<string name="dock_icons">Dock icons</string>
|
<string name="dock_icons">Dock icons</string>
|
||||||
<string name="dock_rows">Dock rows</string>
|
<string name="dock_rows">Dock rows</string>
|
||||||
|
<string name="dock_pages">Dock pages</string>
|
||||||
<string name="state_folded">%1$s (folded)</string>
|
<string name="state_folded">%1$s (folded)</string>
|
||||||
<string name="state_unfolded">%1$s (unfolded)</string>
|
<string name="state_unfolded">%1$s (unfolded)</string>
|
||||||
<string name="folded_label">Folded</string>
|
<string name="folded_label">Folded</string>
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ class DeviceProfileOverrides @Inject constructor(
|
|||||||
val enableTaskbarOnPhone: Boolean,
|
val enableTaskbarOnPhone: Boolean,
|
||||||
|
|
||||||
val numHotseatRows: Int = 1,
|
val numHotseatRows: Int = 1,
|
||||||
|
val numDockPages: Int = 1,
|
||||||
|
|
||||||
// Foldable overrides (-1 means don't override)
|
// Foldable overrides (-1 means don't override)
|
||||||
val foldableShownHotseatIcons: Int = -1,
|
val foldableShownHotseatIcons: Int = -1,
|
||||||
@@ -132,6 +133,7 @@ class DeviceProfileOverrides @Inject constructor(
|
|||||||
enableTaskbarOnPhone = prefs2.enableTaskbarOnPhone.firstCached(),
|
enableTaskbarOnPhone = prefs2.enableTaskbarOnPhone.firstCached(),
|
||||||
|
|
||||||
numHotseatRows = prefs.hotseatRows.get().coerceIn(1, 2),
|
numHotseatRows = prefs.hotseatRows.get().coerceIn(1, 2),
|
||||||
|
numDockPages = prefs.dockPages.get().coerceIn(1, 5),
|
||||||
|
|
||||||
foldableShownHotseatIcons = if (deviceType == InvariantDeviceProfile.TYPE_MULTI_DISPLAY) {
|
foldableShownHotseatIcons = if (deviceType == InvariantDeviceProfile.TYPE_MULTI_DISPLAY) {
|
||||||
val folded = prefs.hotseatColumns.get()
|
val folded = prefs.hotseatColumns.get()
|
||||||
@@ -172,8 +174,8 @@ class DeviceProfileOverrides @Inject constructor(
|
|||||||
idp.numDatabaseAllAppsColumns = foldableDatabaseAllAppsColumns
|
idp.numDatabaseAllAppsColumns = foldableDatabaseAllAppsColumns
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure database can hold enough icons for multi-row dock
|
// Ensure database can hold enough icons for multi-row / multi-page dock
|
||||||
val requiredSlots = idp.numShownHotseatIcons * numHotseatRows
|
val requiredSlots = idp.numShownHotseatIcons * numHotseatRows * numDockPages
|
||||||
if (idp.numDatabaseHotseatIcons < requiredSlots) {
|
if (idp.numDatabaseHotseatIcons < requiredSlots) {
|
||||||
idp.numDatabaseHotseatIcons = requiredSlots
|
idp.numDatabaseHotseatIcons = requiredSlots
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package app.lawnchair.hotseat
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.android.launcher3.CellLayout
|
||||||
|
import com.android.launcher3.DeviceProfile
|
||||||
|
import com.android.launcher3.PagedView
|
||||||
|
import com.android.launcher3.R
|
||||||
|
import com.android.launcher3.Workspace
|
||||||
|
import com.android.launcher3.pageindicators.PageIndicatorDots
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Horizontally pageable container for dock [CellLayout] pages.
|
||||||
|
*/
|
||||||
|
class HotseatPagedView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyle: Int = 0,
|
||||||
|
) : PagedView<PageIndicatorDots>(context, attrs, defStyle) {
|
||||||
|
|
||||||
|
fun interface OnDockPageChangeListener {
|
||||||
|
fun onDockPageChanged(page: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isPagingEnabled: Boolean = false
|
||||||
|
private set(value) {
|
||||||
|
field = value
|
||||||
|
applyPageIndicatorVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var onDockPageChangeListener: OnDockPageChangeListener? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPageIndicator(indicator: PageIndicatorDots?) {
|
||||||
|
mPageIndicator = indicator
|
||||||
|
mPageIndicator?.let {
|
||||||
|
it.setMarkersCount(childCount)
|
||||||
|
it.setActiveMarker(nextPage)
|
||||||
|
}
|
||||||
|
applyPageIndicatorVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyPageIndicatorVisibility() {
|
||||||
|
val indicator = mPageIndicator ?: return
|
||||||
|
if (isPagingEnabled) {
|
||||||
|
indicator.visibility = VISIBLE
|
||||||
|
// Same as workspace: show while paging, fade out when idle.
|
||||||
|
indicator.setShouldAutoHide(true)
|
||||||
|
} else {
|
||||||
|
indicator.setShouldAutoHide(false)
|
||||||
|
indicator.visibility = GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPageAt(index: Int): CellLayout? = getChildAt(index) as? CellLayout
|
||||||
|
|
||||||
|
fun getCurrentCellLayout(): CellLayout? = getPageAt(nextPage)
|
||||||
|
|
||||||
|
fun resetPages(hasVerticalHotseat: Boolean, workspace: Workspace<*>?, dp: DeviceProfile) {
|
||||||
|
removeAllViews()
|
||||||
|
val pageCount = if (hasVerticalHotseat) 1 else maxOf(1, dp.numHotseatPages)
|
||||||
|
isPagingEnabled = pageCount > 1
|
||||||
|
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
for (i in 0 until pageCount) {
|
||||||
|
val page = inflater.inflate(R.layout.hotseat_page, this, false) as CellLayout
|
||||||
|
page.setHotseatPageIndex(i)
|
||||||
|
if (workspace != null) {
|
||||||
|
page.setCellLayoutContainer(workspace)
|
||||||
|
}
|
||||||
|
page.resetCellSize(dp)
|
||||||
|
page.isLongClickable = false
|
||||||
|
page.isHapticFeedbackEnabled = false
|
||||||
|
if (hasVerticalHotseat) {
|
||||||
|
page.setGridSize(1, dp.numShownHotseatIcons)
|
||||||
|
} else {
|
||||||
|
page.setGridSize(dp.numShownHotseatIcons, dp.numHotseatRows)
|
||||||
|
}
|
||||||
|
addView(page, LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
|
||||||
|
}
|
||||||
|
|
||||||
|
mPageIndicator?.let {
|
||||||
|
it.setMarkersCount(pageCount)
|
||||||
|
it.setActiveMarker(0)
|
||||||
|
}
|
||||||
|
applyPageIndicatorVisibility()
|
||||||
|
setCurrentPage(0)
|
||||||
|
requestLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnDockPageChangeListener(listener: OnDockPageChangeListener?) {
|
||||||
|
onDockPageChangeListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageEndTransition() {
|
||||||
|
super.onPageEndTransition()
|
||||||
|
onDockPageChangeListener?.onDockPageChanged(nextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
|
||||||
|
super.onScrollChanged(l, t, oldl, oldt)
|
||||||
|
if (mMaxScroll > 0) {
|
||||||
|
mPageIndicator?.setScroll(l, mMaxScroll)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||||
|
if (!isPagingEnabled) return false
|
||||||
|
return super.onInterceptTouchEvent(ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||||
|
if (!isPagingEnabled) return false
|
||||||
|
return super.onTouchEvent(ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the page CellLayout under the given x in this view's coordinates. */
|
||||||
|
fun findPageAtLocalX(x: Float): CellLayout? {
|
||||||
|
val width = measuredWidth
|
||||||
|
if (width <= 0 || childCount == 0) {
|
||||||
|
return getCurrentCellLayout()
|
||||||
|
}
|
||||||
|
var raw = (scrollX + x).toInt() / width
|
||||||
|
if (mIsRtl) {
|
||||||
|
raw = childCount - 1 - raw
|
||||||
|
}
|
||||||
|
val page = raw.coerceIn(0, childCount - 1)
|
||||||
|
return getPageAt(page)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,6 +101,7 @@ class PreferenceManager @Inject constructor(
|
|||||||
val hotseatColumns = IntPref("pref_hotseatColumns", calculatedGridSpec.hotseatColumns, reloadGrid)
|
val hotseatColumns = IntPref("pref_hotseatColumns", calculatedGridSpec.hotseatColumns, reloadGrid)
|
||||||
val hotseatColumnsUnfolded = IntPref("pref_hotseatColumnsUnfolded", calculatedGridSpec.hotseatColumnsUnfolded, reloadGrid)
|
val hotseatColumnsUnfolded = IntPref("pref_hotseatColumnsUnfolded", calculatedGridSpec.hotseatColumnsUnfolded, reloadGrid)
|
||||||
val hotseatRows = IntPref("pref_hotseatRows", 1, reloadGrid)
|
val hotseatRows = IntPref("pref_hotseatRows", 1, reloadGrid)
|
||||||
|
val dockPages = IntPref("pref_dockPages", 1, reloadGrid)
|
||||||
val workspaceColumns = IntPref("pref_workspaceColumns", calculatedGridSpec.workspaceColumns)
|
val workspaceColumns = IntPref("pref_workspaceColumns", calculatedGridSpec.workspaceColumns)
|
||||||
val workspaceRows = IntPref("pref_workspaceRows", calculatedGridSpec.workspaceRows)
|
val workspaceRows = IntPref("pref_workspaceRows", calculatedGridSpec.workspaceRows)
|
||||||
val workspaceIncreaseMaxGridSize = BoolPref("pref_workspace_increase_max_grid_size", false)
|
val workspaceIncreaseMaxGridSize = BoolPref("pref_workspace_increase_max_grid_size", false)
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ fun GridSettings(prefs: PreferenceManager, prefs2: PreferenceManager2) {
|
|||||||
val hotseatColumnsAdapter = prefs.hotseatColumns.getAdapter()
|
val hotseatColumnsAdapter = prefs.hotseatColumns.getAdapter()
|
||||||
val hotseatColumnsUnfoldedAdapter = prefs.hotseatColumnsUnfolded.getAdapter()
|
val hotseatColumnsUnfoldedAdapter = prefs.hotseatColumnsUnfolded.getAdapter()
|
||||||
val hotseatRowsAdapter = prefs.hotseatRows.getAdapter()
|
val hotseatRowsAdapter = prefs.hotseatRows.getAdapter()
|
||||||
|
val dockPagesAdapter = prefs.dockPages.getAdapter()
|
||||||
|
|
||||||
PreferenceGroup(heading = stringResource(id = R.string.grid)) {
|
PreferenceGroup(heading = stringResource(id = R.string.grid)) {
|
||||||
if (isFoldable) {
|
if (isFoldable) {
|
||||||
@@ -175,6 +176,12 @@ fun GridSettings(prefs: PreferenceManager, prefs2: PreferenceManager2) {
|
|||||||
step = 1,
|
step = 1,
|
||||||
valueRange = 1..2,
|
valueRange = 1..2,
|
||||||
)
|
)
|
||||||
|
SliderPreference(
|
||||||
|
label = stringResource(id = R.string.dock_pages),
|
||||||
|
adapter = dockPagesAdapter,
|
||||||
|
step = 1,
|
||||||
|
valueRange = 1..5,
|
||||||
|
)
|
||||||
SliderPreference(
|
SliderPreference(
|
||||||
adapter = prefs2.hotseatBottomFactor.getAdapter(),
|
adapter = prefs2.hotseatBottomFactor.getAdapter(),
|
||||||
label = stringResource(id = R.string.hotseat_bottom_space_label),
|
label = stringResource(id = R.string.hotseat_bottom_space_label),
|
||||||
@@ -199,12 +206,14 @@ fun ColumnScope.DockPreferencesPreview(modifier: Modifier = Modifier) {
|
|||||||
val prefs2 = preferenceManager2()
|
val prefs2 = preferenceManager2()
|
||||||
|
|
||||||
val hotseatRows = prefs.hotseatRows
|
val hotseatRows = prefs.hotseatRows
|
||||||
|
val dockPages = prefs.dockPages
|
||||||
|
|
||||||
val adapters = listOf(
|
val adapters = listOf(
|
||||||
prefs2.hotseatMode.getAdapter(),
|
prefs2.hotseatMode.getAdapter(),
|
||||||
prefs.hotseatColumns.getAdapter(),
|
prefs.hotseatColumns.getAdapter(),
|
||||||
prefs.hotseatColumnsUnfolded.getAdapter(),
|
prefs.hotseatColumnsUnfolded.getAdapter(),
|
||||||
hotseatRows.getAdapter(),
|
hotseatRows.getAdapter(),
|
||||||
|
dockPages.getAdapter(),
|
||||||
prefs2.themedHotseatQsb.getAdapter(),
|
prefs2.themedHotseatQsb.getAdapter(),
|
||||||
prefs.hotseatQsbCornerRadius.getAdapter(),
|
prefs.hotseatQsbCornerRadius.getAdapter(),
|
||||||
prefs.hotseatQsbAlpha.getAdapter(),
|
prefs.hotseatQsbAlpha.getAdapter(),
|
||||||
|
|||||||
@@ -96,23 +96,53 @@ public class HotseatEduController {
|
|||||||
pageId = mLauncher.getModel().getModelDbController().getNewScreenId();
|
pageId = mLauncher.getModel().getModelDbController().getNewScreenId();
|
||||||
}
|
}
|
||||||
boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout();
|
boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout();
|
||||||
int hotseatItemsNum = mLauncher.getDeviceProfile().numShownHotseatIcons;
|
int numColumns = mLauncher.getDeviceProfile().inv.numColumns;
|
||||||
for (int i = 0; i < hotseatItemsNum; i++) {
|
int destIndex = 0;
|
||||||
int x = isPortrait ? i : 0;
|
for (CellLayout page : mHotseat.getPageLayouts()) {
|
||||||
int y = isPortrait ? 0 : hotseatItemsNum - i - 1;
|
int countX = page.getCountX();
|
||||||
View child = mHotseat.getChildAt(x, y);
|
int countY = page.getCountY();
|
||||||
if (child == null || child.getTag() == null) continue;
|
if (isPortrait) {
|
||||||
ItemInfo tag = (ItemInfo) child.getTag();
|
for (int y = 0; y < countY; y++) {
|
||||||
if (tag.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) continue;
|
for (int x = 0; x < countX; x++) {
|
||||||
mLauncher.getModelWriter().moveItemInDatabase(tag,
|
destIndex += migrateHotseatChild(page.getChildAt(x, y), pageId, toRow,
|
||||||
LauncherSettings.Favorites.CONTAINER_DESKTOP, pageId, i, toRow);
|
destIndex, numColumns);
|
||||||
mNewItems.add(tag);
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < countY; i++) {
|
||||||
|
destIndex += migrateHotseatChild(page.getChildAt(0, countY - i - 1), pageId,
|
||||||
|
toRow, destIndex, numColumns);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return pageId;
|
return pageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return 1 if the item was migrated, otherwise 0 */
|
||||||
|
private int migrateHotseatChild(View child, int pageId, int toRow, int destIndex,
|
||||||
|
int numColumns) {
|
||||||
|
if (child == null || child.getTag() == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ItemInfo tag = (ItemInfo) child.getTag();
|
||||||
|
if (tag.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int destX = numColumns > 0 ? destIndex % numColumns : destIndex;
|
||||||
|
int destY = numColumns > 0 ? toRow - destIndex / numColumns : toRow;
|
||||||
|
if (destY < 0) {
|
||||||
|
destY = 0;
|
||||||
|
}
|
||||||
|
mLauncher.getModelWriter().moveItemInDatabase(tag,
|
||||||
|
LauncherSettings.Favorites.CONTAINER_DESKTOP, pageId, destX, destY);
|
||||||
|
mNewItems.add(tag);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
void moveHotseatItems() {
|
void moveHotseatItems() {
|
||||||
mHotseat.removeAllViewsInLayout();
|
for (CellLayout page : mHotseat.getPageLayouts()) {
|
||||||
|
page.removeAllViewsInLayout();
|
||||||
|
}
|
||||||
if (!mNewItems.isEmpty()) {
|
if (!mNewItems.isEmpty()) {
|
||||||
mLauncher.bindItemsAdded(mNewItems);
|
mLauncher.bindItemsAdded(mNewItems);
|
||||||
}
|
}
|
||||||
@@ -123,8 +153,9 @@ public class HotseatEduController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void showDimissTip() {
|
void showDimissTip() {
|
||||||
if (mHotseat.getShortcutsAndWidgets().getChildCount()
|
CellLayout page = mHotseat.getCurrentPageLayout();
|
||||||
< mLauncher.getDeviceProfile().numShownHotseatIcons) {
|
int childCount = page != null ? page.getShortcutsAndWidgets().getChildCount() : 0;
|
||||||
|
if (childCount < mLauncher.getDeviceProfile().numShownHotseatIcons) {
|
||||||
Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
|
Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
|
||||||
R.string.hotseat_prediction_settings, null,
|
R.string.hotseat_prediction_settings, null,
|
||||||
() -> mLauncher.startActivity(getSettingsIntent()));
|
() -> mLauncher.startActivity(getSettingsIntent()));
|
||||||
@@ -138,11 +169,12 @@ public class HotseatEduController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void showEdu() {
|
void showEdu() {
|
||||||
int childCount = mHotseat.getShortcutsAndWidgets().getChildCount();
|
CellLayout page = mHotseat.getCurrentPageLayout();
|
||||||
|
int childCount = page != null ? page.getShortcutsAndWidgets().getChildCount() : 0;
|
||||||
CellLayout cellLayout = mLauncher.getWorkspace().getScreenWithId(Workspace.FIRST_SCREEN_ID);
|
CellLayout cellLayout = mLauncher.getWorkspace().getScreenWithId(Workspace.FIRST_SCREEN_ID);
|
||||||
// hotseat is already empty and does not require migration. show edu tip
|
// hotseat is already empty and does not require migration. show edu tip
|
||||||
boolean requiresMigration = IntStream.range(0, childCount).anyMatch(i -> {
|
boolean requiresMigration = IntStream.range(0, childCount).anyMatch(i -> {
|
||||||
View v = mHotseat.getShortcutsAndWidgets().getChildAt(i);
|
View v = page.getShortcutsAndWidgets().getChildAt(i);
|
||||||
return v != null && v.getTag() != null && ((ItemInfo) v.getTag()).container
|
return v != null && v.getTag() != null && ((ItemInfo) v.getTag()).container
|
||||||
!= LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
|
!= LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
|
||||||
});
|
});
|
||||||
@@ -168,14 +200,19 @@ public class HotseatEduController {
|
|||||||
* @return whether suitable child was found and tip was shown
|
* @return whether suitable child was found and tip was shown
|
||||||
*/
|
*/
|
||||||
private boolean showHotseatArrowTip(boolean usePinned, String message) {
|
private boolean showHotseatArrowTip(boolean usePinned, String message) {
|
||||||
int childCount = mHotseat.getShortcutsAndWidgets().getChildCount();
|
CellLayout page = mHotseat.getCurrentPageLayout();
|
||||||
|
if (page == null) {
|
||||||
|
Log.e(TAG, "Unable to find suitable view for ArrowTip");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int childCount = page.getShortcutsAndWidgets().getChildCount();
|
||||||
boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout();
|
boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout();
|
||||||
|
|
||||||
BubbleTextView tipTargetView = null;
|
BubbleTextView tipTargetView = null;
|
||||||
for (int i = childCount - 1; i > -1; i--) {
|
for (int i = childCount - 1; i > -1; i--) {
|
||||||
int x = isPortrait ? i : 0;
|
int x = isPortrait ? i : 0;
|
||||||
int y = isPortrait ? 0 : i;
|
int y = isPortrait ? 0 : i;
|
||||||
View v = mHotseat.getShortcutsAndWidgets().getChildAt(x, y);
|
View v = page.getShortcutsAndWidgets().getChildAt(x, y);
|
||||||
if (v instanceof BubbleTextView && v.getTag() instanceof WorkspaceItemInfo) {
|
if (v instanceof BubbleTextView && v.getTag() instanceof WorkspaceItemInfo) {
|
||||||
ItemInfo info = (ItemInfo) v.getTag();
|
ItemInfo info = (ItemInfo) v.getTag();
|
||||||
boolean isPinned = info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
boolean isPinned = info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||||
|
|||||||
+106
-33
@@ -42,6 +42,7 @@ import com.android.launcher3.DeviceProfile;
|
|||||||
import com.android.launcher3.DragSource;
|
import com.android.launcher3.DragSource;
|
||||||
import com.android.launcher3.DropTarget;
|
import com.android.launcher3.DropTarget;
|
||||||
import com.android.launcher3.Flags;
|
import com.android.launcher3.Flags;
|
||||||
|
import com.android.launcher3.CellLayout;
|
||||||
import com.android.launcher3.Hotseat;
|
import com.android.launcher3.Hotseat;
|
||||||
import com.android.launcher3.LauncherPrefs;
|
import com.android.launcher3.LauncherPrefs;
|
||||||
import com.android.launcher3.LauncherSettings;
|
import com.android.launcher3.LauncherSettings;
|
||||||
@@ -98,7 +99,7 @@ public class HotseatPredictionController implements DragController.DragListener,
|
|||||||
private AnimatorSet mIconRemoveAnimators;
|
private AnimatorSet mIconRemoveAnimators;
|
||||||
private int mPauseFlags = 0;
|
private int mPauseFlags = 0;
|
||||||
|
|
||||||
private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
|
private final List<OutlineOnPage> mOutlineDrawings = new ArrayList<>();
|
||||||
|
|
||||||
private boolean mEnableHotseatLongPressTipForTesting = true;
|
private boolean mEnableHotseatLongPressTipForTesting = true;
|
||||||
|
|
||||||
@@ -133,7 +134,40 @@ public class HotseatPredictionController implements DragController.DragListener,
|
|||||||
mLauncher.getDragController().addDragListener(this);
|
mLauncher.getDragController().addDragListener(this);
|
||||||
|
|
||||||
launcher.addOnDeviceProfileChangeListener(this);
|
launcher.addOnDeviceProfileChangeListener(this);
|
||||||
mHotseat.getShortcutsAndWidgets().setOnHierarchyChangeListener(this);
|
attachPageListeners();
|
||||||
|
mHotseat.getPagedView().setOnDockPageChangeListener(page -> {
|
||||||
|
if (mPauseFlags == 0 && !mLauncher.isWorkspaceLoading()) {
|
||||||
|
fillGapsWithPrediction(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attachPageListeners() {
|
||||||
|
for (CellLayout page : mHotseat.getPageLayouts()) {
|
||||||
|
page.getShortcutsAndWidgets().setOnHierarchyChangeListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private CellLayout getPageForRank(int rank) {
|
||||||
|
return mHotseat.getPageAt(mHotseat.getPageFromOrder(rank));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private View getChildAtRank(int rank) {
|
||||||
|
CellLayout page = getPageForRank(rank);
|
||||||
|
if (page == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return page.getChildAt(
|
||||||
|
mHotseat.getCellXFromOrder(rank),
|
||||||
|
mHotseat.getCellYFromOrder(rank));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Visible-page slot count (columns × rows). Predictions only fill the current page. */
|
||||||
|
private int getVisiblePageStartRank() {
|
||||||
|
int page = mHotseat.getPagedView().getNextPage();
|
||||||
|
return Math.max(0, page) * mHotSeatItemsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -211,10 +245,10 @@ public class HotseatPredictionController implements DragController.DragListener,
|
|||||||
}
|
}
|
||||||
|
|
||||||
mPauseFlags |= FLAG_FILL_IN_PROGRESS;
|
mPauseFlags |= FLAG_FILL_IN_PROGRESS;
|
||||||
for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
|
int startRank = getVisiblePageStartRank();
|
||||||
View child = mHotseat.getChildAt(
|
for (int localRank = 0; localRank < mHotSeatItemsCount; localRank++) {
|
||||||
mHotseat.getCellXFromOrder(rank),
|
int rank = startRank + localRank;
|
||||||
mHotseat.getCellYFromOrder(rank));
|
View child = getChildAtRank(rank);
|
||||||
|
|
||||||
if (child != null && !isPredictedIcon(child)) {
|
if (child != null && !isPredictedIcon(child)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -222,7 +256,10 @@ public class HotseatPredictionController implements DragController.DragListener,
|
|||||||
if (mPredictedItems.size() <= predictionIndex) {
|
if (mPredictedItems.size() <= predictionIndex) {
|
||||||
// Remove predicted apps from the past
|
// Remove predicted apps from the past
|
||||||
if (isPredictedIcon(child)) {
|
if (isPredictedIcon(child)) {
|
||||||
mHotseat.removeView(child);
|
CellLayout page = getPageForRank(rank);
|
||||||
|
if (page != null) {
|
||||||
|
page.removeView(child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -270,10 +307,10 @@ public class HotseatPredictionController implements DragController.DragListener,
|
|||||||
|
|
||||||
private void removeOutlineDrawings() {
|
private void removeOutlineDrawings() {
|
||||||
if (mOutlineDrawings.isEmpty()) return;
|
if (mOutlineDrawings.isEmpty()) return;
|
||||||
for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
|
for (OutlineOnPage outline : mOutlineDrawings) {
|
||||||
mHotseat.removeDelegatedCellDrawing(outlineDrawing);
|
outline.page.removeDelegatedCellDrawing(outline.drawing);
|
||||||
|
outline.page.invalidate();
|
||||||
}
|
}
|
||||||
mHotseat.invalidate();
|
|
||||||
mOutlineDrawings.clear();
|
mOutlineDrawings.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,9 +337,7 @@ public class HotseatPredictionController implements DragController.DragListener,
|
|||||||
* Pins a predicted app icon into place.
|
* Pins a predicted app icon into place.
|
||||||
*/
|
*/
|
||||||
public void pinPrediction(ItemInfo info) {
|
public void pinPrediction(ItemInfo info) {
|
||||||
PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt(
|
PredictedAppIcon icon = (PredictedAppIcon) getChildAtRank(info.rank);
|
||||||
mHotseat.getCellXFromOrder(info.rank),
|
|
||||||
mHotseat.getCellYFromOrder(info.rank));
|
|
||||||
if (icon == null) {
|
if (icon == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -317,26 +352,28 @@ public class HotseatPredictionController implements DragController.DragListener,
|
|||||||
.log(LAUNCHER_HOTSEAT_PREDICTION_PINNED);
|
.log(LAUNCHER_HOTSEAT_PREDICTION_PINNED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<PredictedAppIcon> getPredictedIcons() {
|
private List<PredictedIconOnPage> getPredictedIcons() {
|
||||||
List<PredictedAppIcon> icons = new ArrayList<>();
|
List<PredictedIconOnPage> icons = new ArrayList<>();
|
||||||
ViewGroup vg = mHotseat.getShortcutsAndWidgets();
|
for (CellLayout page : mHotseat.getPageLayouts()) {
|
||||||
for (int i = 0; i < vg.getChildCount(); i++) {
|
ViewGroup vg = page.getShortcutsAndWidgets();
|
||||||
View child = vg.getChildAt(i);
|
for (int i = 0; i < vg.getChildCount(); i++) {
|
||||||
if (isPredictedIcon(child)) {
|
View child = vg.getChildAt(i);
|
||||||
icons.add((PredictedAppIcon) child);
|
if (isPredictedIcon(child)) {
|
||||||
|
icons.add(new PredictedIconOnPage(page, (PredictedAppIcon) child));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return icons;
|
return icons;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removePredictedApps(List<PredictedAppIcon.PredictedIconOutlineDrawing> outlines,
|
private void removePredictedApps(DropTarget.DragObject dragObject) {
|
||||||
DropTarget.DragObject dragObject) {
|
|
||||||
if (mIconRemoveAnimators != null) {
|
if (mIconRemoveAnimators != null) {
|
||||||
mIconRemoveAnimators.end();
|
mIconRemoveAnimators.end();
|
||||||
}
|
}
|
||||||
mIconRemoveAnimators = new AnimatorSet();
|
mIconRemoveAnimators = new AnimatorSet();
|
||||||
removeOutlineDrawings();
|
removeOutlineDrawings();
|
||||||
for (PredictedAppIcon icon : getPredictedIcons()) {
|
for (PredictedIconOnPage entry : getPredictedIcons()) {
|
||||||
|
PredictedAppIcon icon = entry.icon;
|
||||||
if (!icon.isEnabled()) {
|
if (!icon.isEnabled()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -345,8 +382,10 @@ public class HotseatPredictionController implements DragController.DragListener,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
|
int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
|
||||||
outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing(
|
mOutlineDrawings.add(new OutlineOnPage(entry.page,
|
||||||
mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon));
|
new PredictedAppIcon.PredictedIconOutlineDrawing(
|
||||||
|
mHotseat.getCellXFromOrder(rank),
|
||||||
|
mHotseat.getCellYFromOrder(rank), icon)));
|
||||||
icon.setEnabled(false);
|
icon.setEnabled(false);
|
||||||
ObjectAnimator animator = ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0);
|
ObjectAnimator animator = ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0);
|
||||||
animator.addListener(new AnimationSuccessListener() {
|
animator.addListener(new AnimationSuccessListener() {
|
||||||
@@ -369,19 +408,28 @@ public class HotseatPredictionController implements DragController.DragListener,
|
|||||||
*/
|
*/
|
||||||
private void removeIconWithoutNotify(PredictedAppIcon icon) {
|
private void removeIconWithoutNotify(PredictedAppIcon icon) {
|
||||||
mPauseFlags |= FLAG_REMOVING_PREDICTED_ICON;
|
mPauseFlags |= FLAG_REMOVING_PREDICTED_ICON;
|
||||||
mHotseat.removeView(icon);
|
ViewGroup parent = (ViewGroup) icon.getParent();
|
||||||
|
if (parent != null) {
|
||||||
|
// Parent is ShortcutAndWidgetContainer; remove via CellLayout.
|
||||||
|
ViewGroup cellLayout = (ViewGroup) parent.getParent();
|
||||||
|
if (cellLayout instanceof CellLayout) {
|
||||||
|
cellLayout.removeView(icon);
|
||||||
|
} else {
|
||||||
|
parent.removeView(icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
mPauseFlags &= ~FLAG_REMOVING_PREDICTED_ICON;
|
mPauseFlags &= ~FLAG_REMOVING_PREDICTED_ICON;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
|
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
|
||||||
removePredictedApps(mOutlineDrawings, dragObject);
|
removePredictedApps(dragObject);
|
||||||
if (mOutlineDrawings.isEmpty()) return;
|
if (mOutlineDrawings.isEmpty()) return;
|
||||||
for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
|
for (OutlineOnPage outline : mOutlineDrawings) {
|
||||||
mHotseat.addDelegatedCellDrawing(outlineDrawing);
|
outline.page.addDelegatedCellDrawing(outline.drawing);
|
||||||
|
outline.page.invalidate();
|
||||||
}
|
}
|
||||||
mPauseFlags |= FLAG_DRAG_IN_PROGRESS;
|
mPauseFlags |= FLAG_DRAG_IN_PROGRESS;
|
||||||
mHotseat.invalidate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -419,6 +467,7 @@ public class HotseatPredictionController implements DragController.DragListener,
|
|||||||
@Override
|
@Override
|
||||||
public void onDeviceProfileChanged(DeviceProfile profile) {
|
public void onDeviceProfileChanged(DeviceProfile profile) {
|
||||||
this.mHotSeatItemsCount = profile.numShownHotseatIcons * profile.numHotseatRows;
|
this.mHotSeatItemsCount = profile.numShownHotseatIcons * profile.numHotseatRows;
|
||||||
|
attachPageListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -447,8 +496,8 @@ public class HotseatPredictionController implements DragController.DragListener,
|
|||||||
}
|
}
|
||||||
|
|
||||||
int cardinality = 0;
|
int cardinality = 0;
|
||||||
for (PredictedAppIcon icon : getPredictedIcons()) {
|
for (PredictedIconOnPage entry : getPredictedIcons()) {
|
||||||
ItemInfo info = (ItemInfo) icon.getTag();
|
ItemInfo info = (ItemInfo) entry.icon.getTag();
|
||||||
cardinality |= 1 << info.screenId;
|
cardinality |= 1 << info.screenId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,7 +531,11 @@ public class HotseatPredictionController implements DragController.DragListener,
|
|||||||
* Called when user completes adding item requiring a config activity to the hotseat
|
* Called when user completes adding item requiring a config activity to the hotseat
|
||||||
*/
|
*/
|
||||||
public void onDeferredDrop(int cellX, int cellY) {
|
public void onDeferredDrop(int cellX, int cellY) {
|
||||||
View child = mHotseat.getChildAt(cellX, cellY);
|
CellLayout page = mHotseat.getCurrentPageLayout();
|
||||||
|
if (page == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
View child = page.getChildAt(cellX, cellY);
|
||||||
if (child instanceof PredictedAppIcon) {
|
if (child instanceof PredictedAppIcon) {
|
||||||
removeIconWithoutNotify((PredictedAppIcon) child);
|
removeIconWithoutNotify((PredictedAppIcon) child);
|
||||||
}
|
}
|
||||||
@@ -534,4 +587,24 @@ public class HotseatPredictionController implements DragController.DragListener,
|
|||||||
writer.println(prefix + "\t\t" + info);
|
writer.println(prefix + "\t\t" + info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class PredictedIconOnPage {
|
||||||
|
final CellLayout page;
|
||||||
|
final PredictedAppIcon icon;
|
||||||
|
|
||||||
|
PredictedIconOnPage(CellLayout page, PredictedAppIcon icon) {
|
||||||
|
this.page = page;
|
||||||
|
this.icon = icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class OutlineOnPage {
|
||||||
|
final CellLayout page;
|
||||||
|
final PredictedAppIcon.PredictedIconOutlineDrawing drawing;
|
||||||
|
|
||||||
|
OutlineOnPage(CellLayout page, PredictedAppIcon.PredictedIconOutlineDrawing drawing) {
|
||||||
|
this.page = page;
|
||||||
|
this.drawing = drawing;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,11 @@ object UnfoldAnimationBuilder {
|
|||||||
LINEAR
|
LINEAR
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
registerViews(hotseat)
|
setClipChildren(hotseat, false, restoreList)
|
||||||
|
setClipToPadding(hotseat, false, restoreList)
|
||||||
|
for (page in hotseat.pageLayouts) {
|
||||||
|
registerViews(page)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
anim.addEndListener { restoreList.forEach { it.action(it.target, it.value) } }
|
anim.addEndListener { restoreList.forEach { it.action(it.target, it.value) } }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,11 @@
|
|||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
|
Modifications copyright 2026 Lawnchair
|
||||||
-->
|
-->
|
||||||
<com.android.launcher3.Hotseat
|
<com.android.launcher3.Hotseat
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:id="@+id/hotseat"
|
android:id="@+id/hotseat"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@@ -23,4 +24,4 @@
|
|||||||
android:importantForAccessibility="no"
|
android:importantForAccessibility="no"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:longClickable="false"
|
android:longClickable="false"
|
||||||
launcher:containerType="hotseat" />
|
android:clipChildren="false" />
|
||||||
@@ -191,6 +191,8 @@ public class CellLayout extends ViewGroup {
|
|||||||
public static final int FOLDER = 2;
|
public static final int FOLDER = 2;
|
||||||
|
|
||||||
@ContainerType private final int mContainerType;
|
@ContainerType private final int mContainerType;
|
||||||
|
/** Page index within a multi-page hotseat; unused for non-hotseat layouts. */
|
||||||
|
private int mHotseatPageIndex = 0;
|
||||||
|
|
||||||
public static final float DEFAULT_SCALE = 1f;
|
public static final float DEFAULT_SCALE = 1f;
|
||||||
|
|
||||||
@@ -336,6 +338,20 @@ public class CellLayout extends ViewGroup {
|
|||||||
mCellLayoutContainer = cellLayoutContainer;
|
mCellLayoutContainer = cellLayoutContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sets which dock page this layout represents when used as a hotseat page. */
|
||||||
|
public void setHotseatPageIndex(int pageIndex) {
|
||||||
|
mHotseatPageIndex = pageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the dock page index for multi-page hotseat layouts. */
|
||||||
|
public int getHotseatPageIndex() {
|
||||||
|
return mHotseatPageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHotseat() {
|
||||||
|
return mContainerType == HOTSEAT;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets or clears a delegate used for accessible drag and drop
|
* Sets or clears a delegate used for accessible drag and drop
|
||||||
*/
|
*/
|
||||||
@@ -1473,7 +1489,7 @@ public class CellLayout extends ViewGroup {
|
|||||||
int container = Favorites.CONTAINER_DESKTOP;
|
int container = Favorites.CONTAINER_DESKTOP;
|
||||||
|
|
||||||
if (mContainerType == HOTSEAT) {
|
if (mContainerType == HOTSEAT) {
|
||||||
screenId = -1;
|
screenId = mHotseatPageIndex;
|
||||||
container = Favorites.CONTAINER_HOTSEAT;
|
container = Favorites.CONTAINER_HOTSEAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -221,6 +221,8 @@ public class DeviceProfile {
|
|||||||
private final HotseatProfile hotseatProfile;
|
private final HotseatProfile hotseatProfile;
|
||||||
public int numShownHotseatIcons;
|
public int numShownHotseatIcons;
|
||||||
public int numHotseatRows;
|
public int numHotseatRows;
|
||||||
|
/** Number of horizontally scrollable dock pages (1 = classic single dock). */
|
||||||
|
public int numHotseatPages;
|
||||||
public int hotseatCellHeightPx;
|
public int hotseatCellHeightPx;
|
||||||
private int mHotseatColumnSpan;
|
private int mHotseatColumnSpan;
|
||||||
private int mHotseatWidthPx; // not used in vertical bar layout
|
private int mHotseatWidthPx; // not used in vertical bar layout
|
||||||
@@ -331,6 +333,7 @@ public class DeviceProfile {
|
|||||||
hotseatQsbWidth = 0;
|
hotseatQsbWidth = 0;
|
||||||
hotseatBorderSpace = 0;
|
hotseatBorderSpace = 0;
|
||||||
numHotseatRows = 1;
|
numHotseatRows = 1;
|
||||||
|
numHotseatPages = 1;
|
||||||
mBubbleBarSpaceThresholdPx = 0;
|
mBubbleBarSpaceThresholdPx = 0;
|
||||||
numShownAllAppsColumns = 0;
|
numShownAllAppsColumns = 0;
|
||||||
mViewScaleProvider = null;
|
mViewScaleProvider = null;
|
||||||
@@ -517,6 +520,7 @@ public class DeviceProfile {
|
|||||||
|
|
||||||
numShownHotseatIcons = displayOptionSpec.numShownHotseatIcons;
|
numShownHotseatIcons = displayOptionSpec.numShownHotseatIcons;
|
||||||
numHotseatRows = Math.max(1, Math.min(2, PreferenceManager.getInstance(context).getHotseatRows().get()));
|
numHotseatRows = Math.max(1, Math.min(2, PreferenceManager.getInstance(context).getHotseatRows().get()));
|
||||||
|
numHotseatPages = Math.max(1, Math.min(5, PreferenceManager.getInstance(context).getDockPages().get()));
|
||||||
mHotseatColumnSpan = inv.numColumns;
|
mHotseatColumnSpan = inv.numColumns;
|
||||||
|
|
||||||
numShownAllAppsColumns = displayOptionSpec.numAllAppsColumns;
|
numShownAllAppsColumns = displayOptionSpec.numAllAppsColumns;
|
||||||
|
|||||||
@@ -27,11 +27,8 @@ import android.animation.AnimatorSet;
|
|||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.animation.ValueAnimator;
|
import android.animation.ValueAnimator;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
|
||||||
import android.graphics.drawable.InsetDrawable;
|
import android.graphics.drawable.InsetDrawable;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
@@ -45,35 +42,34 @@ import android.widget.FrameLayout;
|
|||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.android.launcher3.ShortcutAndWidgetContainer.TranslationProvider;
|
import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
|
||||||
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
|
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
|
||||||
import com.android.launcher3.taskbar.BlurredBitmapDrawable;
|
import com.android.launcher3.pageindicators.PageIndicatorDots;
|
||||||
|
|
||||||
import com.android.launcher3.util.HorizontalInsettableView;
|
import com.android.launcher3.util.HorizontalInsettableView;
|
||||||
import com.android.launcher3.util.MultiPropertyFactory;
|
import com.android.launcher3.util.MultiPropertyFactory;
|
||||||
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
||||||
import com.android.launcher3.util.MultiTranslateDelegate;
|
import com.android.launcher3.util.MultiTranslateDelegate;
|
||||||
import com.android.launcher3.util.MultiValueAlpha;
|
import com.android.launcher3.util.MultiValueAlpha;
|
||||||
import com.android.launcher3.views.ActivityContext;
|
import com.android.launcher3.views.ActivityContext;
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
import com.hoko.blur.HokoBlur;
|
|
||||||
import app.lawnchair.preferences2.PreferenceCacheExtensionsKt;
|
|
||||||
import app.lawnchair.hotseat.DisabledHotseat;
|
import app.lawnchair.hotseat.DisabledHotseat;
|
||||||
import app.lawnchair.hotseat.HotseatMode;
|
import app.lawnchair.hotseat.HotseatMode;
|
||||||
|
import app.lawnchair.hotseat.HotseatPagedView;
|
||||||
import app.lawnchair.hotseat.LawnchairHotseat;
|
import app.lawnchair.hotseat.LawnchairHotseat;
|
||||||
import app.lawnchair.preferences.PreferenceManager;
|
import app.lawnchair.preferences.PreferenceManager;
|
||||||
|
import app.lawnchair.preferences2.PreferenceCacheExtensionsKt;
|
||||||
import app.lawnchair.preferences2.PreferenceManager2;
|
import app.lawnchair.preferences2.PreferenceManager2;
|
||||||
import app.lawnchair.theme.drawable.DrawableTokens;
|
import app.lawnchair.theme.drawable.DrawableTokens;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View class that represents the bottom row of the home screen.
|
* View class that represents the bottom dock of the home screen.
|
||||||
|
* Hosts a {@link app.lawnchair.hotseat.HotseatPagedView} of icon grids plus an optional QSB.
|
||||||
*/
|
*/
|
||||||
public class Hotseat extends CellLayout implements Insettable {
|
public class Hotseat extends FrameLayout implements Insettable {
|
||||||
|
|
||||||
public static final int ALPHA_CHANNEL_TASKBAR_ALIGNMENT = 0;
|
public static final int ALPHA_CHANNEL_TASKBAR_ALIGNMENT = 0;
|
||||||
public static final int ALPHA_CHANNEL_PREVIEW_RENDERER = 1;
|
public static final int ALPHA_CHANNEL_PREVIEW_RENDERER = 1;
|
||||||
@@ -98,11 +94,13 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
// Ratio of empty space, qsb should take up to appear visually centered.
|
// Ratio of empty space, qsb should take up to appear visually centered.
|
||||||
public static final float QSB_CENTER_FACTOR = .325f;
|
public static final float QSB_CENTER_FACTOR = .325f;
|
||||||
private static final int BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS = 250;
|
private static final int BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS = 250;
|
||||||
|
private static final int DOCK_PAGE_INDICATOR_HEIGHT_DP = 8;
|
||||||
|
|
||||||
@ViewDebug.ExportedProperty(category = "launcher")
|
@ViewDebug.ExportedProperty(category = "launcher")
|
||||||
private boolean mHasVerticalHotseat;
|
private boolean mHasVerticalHotseat;
|
||||||
private Workspace<?> mWorkspace;
|
private Workspace<?> mWorkspace;
|
||||||
private boolean mSendTouchToWorkspace;
|
private boolean mSendTouchToWorkspace;
|
||||||
|
private boolean mSendTouchToPager;
|
||||||
private final MultiValueAlpha mIconsAlphaChannels;
|
private final MultiValueAlpha mIconsAlphaChannels;
|
||||||
private final MultiValueAlpha mQsbAlphaChannels;
|
private final MultiValueAlpha mQsbAlphaChannels;
|
||||||
|
|
||||||
@@ -111,6 +109,12 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
private final MultiPropertyFactory mIconsTranslationXFactory;
|
private final MultiPropertyFactory mIconsTranslationXFactory;
|
||||||
|
|
||||||
private final View mQsb;
|
private final View mQsb;
|
||||||
|
private final FrameLayout mIconsContainer;
|
||||||
|
private final HotseatPagedView mPagedView;
|
||||||
|
private final PageIndicatorDots mPageIndicator;
|
||||||
|
private final int mPageIndicatorHeight;
|
||||||
|
|
||||||
|
private final ActivityContext mActivity;
|
||||||
|
|
||||||
PreferenceManager2 preferenceManager2;
|
PreferenceManager2 preferenceManager2;
|
||||||
PreferenceManager preferenceManager;
|
PreferenceManager preferenceManager;
|
||||||
@@ -125,6 +129,7 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
|
|
||||||
public Hotseat(Context context, AttributeSet attrs, int defStyle) {
|
public Hotseat(Context context, AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
|
mActivity = ActivityContext.lookupContext(context);
|
||||||
|
|
||||||
preferenceManager2 = PreferenceManager2.getInstance(context);
|
preferenceManager2 = PreferenceManager2.getInstance(context);
|
||||||
preferenceManager = PreferenceManager.getInstance(context);
|
preferenceManager = PreferenceManager.getInstance(context);
|
||||||
@@ -143,6 +148,29 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
}
|
}
|
||||||
int layoutId = hotseatMode.getLayoutResourceId();
|
int layoutId = hotseatMode.getLayoutResourceId();
|
||||||
|
|
||||||
|
mIconsContainer = new FrameLayout(context);
|
||||||
|
mIconsContainer.setLayoutParams(new LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
mIconsContainer.setClipChildren(false);
|
||||||
|
mIconsContainer.setClipToPadding(false);
|
||||||
|
addView(mIconsContainer);
|
||||||
|
|
||||||
|
mPagedView = new HotseatPagedView(context);
|
||||||
|
mPagedView.setLayoutParams(new LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
mIconsContainer.addView(mPagedView);
|
||||||
|
|
||||||
|
mPageIndicatorHeight = Math.round(
|
||||||
|
DOCK_PAGE_INDICATOR_HEIGHT_DP * getResources().getDisplayMetrics().density);
|
||||||
|
mPageIndicator = new PageIndicatorDots(context);
|
||||||
|
LayoutParams indicatorLp = new LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT, mPageIndicatorHeight);
|
||||||
|
indicatorLp.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
|
||||||
|
mPageIndicator.setLayoutParams(indicatorLp);
|
||||||
|
mPageIndicator.setVisibility(GONE);
|
||||||
|
mIconsContainer.addView(mPageIndicator);
|
||||||
|
mPagedView.setPageIndicator(mPageIndicator);
|
||||||
|
|
||||||
if (Flags.enableQsbOnHotseat()) {
|
if (Flags.enableQsbOnHotseat()) {
|
||||||
mQsb = LayoutInflater.from(context).inflate(R.layout.qsb_container_hotseat, this,
|
mQsb = LayoutInflater.from(context).inflate(R.layout.qsb_container_hotseat, this,
|
||||||
false);
|
false);
|
||||||
@@ -152,19 +180,24 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addView(mQsb);
|
addView(mQsb);
|
||||||
mIconsAlphaChannels = new MultiValueAlpha(getShortcutsAndWidgets(),
|
|
||||||
ALPHA_CHANNEL_CHANNELS_COUNT);
|
// Create an initial page so alpha/translation channels have a target during construction.
|
||||||
|
mPagedView.resetPages(false, null, mActivity.getDeviceProfile());
|
||||||
|
|
||||||
|
mIconsAlphaChannels = new MultiValueAlpha(mIconsContainer, ALPHA_CHANNEL_CHANNELS_COUNT);
|
||||||
mIconsAlphaChannels.setUpdateVisibility(true);
|
mIconsAlphaChannels.setUpdateVisibility(true);
|
||||||
if (mQsb instanceof Reorderable qsbReorderable) {
|
if (mQsb instanceof Reorderable qsbReorderable) {
|
||||||
mQsbTranslationX = qsbReorderable.getTranslateDelegate()
|
mQsbTranslationX = qsbReorderable.getTranslateDelegate()
|
||||||
.getTranslationX(MultiTranslateDelegate.INDEX_NAV_BAR_ANIM);
|
.getTranslationX(MultiTranslateDelegate.INDEX_NAV_BAR_ANIM);
|
||||||
}
|
}
|
||||||
mIconsTranslationXFactory = new MultiPropertyFactory<>(getShortcutsAndWidgets(),
|
mIconsTranslationXFactory = new MultiPropertyFactory<>(mIconsContainer,
|
||||||
VIEW_TRANSLATE_X, ICONS_TRANSLATION_X_CHANNELS_COUNT, Float::sum);
|
VIEW_TRANSLATE_X, ICONS_TRANSLATION_X_CHANNELS_COUNT, Float::sum);
|
||||||
mQsbAlphaChannels = new MultiValueAlpha(mQsb, ALPHA_CHANNEL_CHANNELS_COUNT);
|
mQsbAlphaChannels = new MultiValueAlpha(mQsb, ALPHA_CHANNEL_CHANNELS_COUNT);
|
||||||
mQsbAlphaChannels.setUpdateVisibility(true);
|
mQsbAlphaChannels.setUpdateVisibility(true);
|
||||||
|
|
||||||
setUpBackground();
|
setUpBackground();
|
||||||
|
setClipChildren(false);
|
||||||
|
setClipToPadding(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setUpBackground() {
|
private void setUpBackground() {
|
||||||
@@ -196,6 +229,40 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
return mQsbTranslationX;
|
return mQsbTranslationX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HotseatPagedView getPagedView() {
|
||||||
|
return mPagedView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the CellLayout for the given dock page. */
|
||||||
|
@Nullable
|
||||||
|
public CellLayout getPageAt(int page) {
|
||||||
|
return mPagedView.getPageAt(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the currently visible dock page layout. */
|
||||||
|
@Nullable
|
||||||
|
public CellLayout getCurrentPageLayout() {
|
||||||
|
return mPagedView.getCurrentCellLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns all dock page layouts. */
|
||||||
|
public CellLayout[] getPageLayouts() {
|
||||||
|
int count = mPagedView.getPageCount();
|
||||||
|
CellLayout[] pages = new CellLayout[count];
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
pages[i] = mPagedView.getPageAt(i);
|
||||||
|
}
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether {@code layout} is one of this hotseat's page CellLayouts. */
|
||||||
|
public boolean isHotseatPage(View layout) {
|
||||||
|
if (!(layout instanceof CellLayout)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return layout.getParent() == mPagedView;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns orientation specific cell X given invariant order in the hotseat
|
* Returns orientation specific cell X given invariant order in the hotseat
|
||||||
*/
|
*/
|
||||||
@@ -205,7 +272,8 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
}
|
}
|
||||||
DeviceProfile dp = mActivity.getDeviceProfile();
|
DeviceProfile dp = mActivity.getDeviceProfile();
|
||||||
int numColumns = dp.numShownHotseatIcons;
|
int numColumns = dp.numShownHotseatIcons;
|
||||||
return rank % numColumns;
|
int localRank = getLocalRank(rank, dp);
|
||||||
|
return localRank % numColumns;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -213,11 +281,29 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
*/
|
*/
|
||||||
public int getCellYFromOrder(int rank) {
|
public int getCellYFromOrder(int rank) {
|
||||||
if (mHasVerticalHotseat) {
|
if (mHasVerticalHotseat) {
|
||||||
return getCountY() - (rank + 1);
|
CellLayout page = getCurrentPageLayout();
|
||||||
|
int countY = page != null ? page.getCountY() : mActivity.getDeviceProfile().numShownHotseatIcons;
|
||||||
|
return countY - (rank + 1);
|
||||||
}
|
}
|
||||||
DeviceProfile dp = mActivity.getDeviceProfile();
|
DeviceProfile dp = mActivity.getDeviceProfile();
|
||||||
int numColumns = dp.numShownHotseatIcons;
|
int numColumns = dp.numShownHotseatIcons;
|
||||||
return rank / numColumns;
|
int localRank = getLocalRank(rank, dp);
|
||||||
|
return localRank / numColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the dock page index for a global hotseat rank. */
|
||||||
|
public int getPageFromOrder(int rank) {
|
||||||
|
if (mHasVerticalHotseat) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
DeviceProfile dp = mActivity.getDeviceProfile();
|
||||||
|
int slotsPerPage = Math.max(1, dp.numShownHotseatIcons * dp.numHotseatRows);
|
||||||
|
return rank / slotsPerPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getLocalRank(int rank, DeviceProfile dp) {
|
||||||
|
int slotsPerPage = Math.max(1, dp.numShownHotseatIcons * dp.numHotseatRows);
|
||||||
|
return rank % slotsPerPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isHasVerticalHotseat() {
|
boolean isHasVerticalHotseat() {
|
||||||
@@ -228,45 +314,34 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
ActivityContext activityContext = ActivityContext.lookupContext(getContext());
|
ActivityContext activityContext = ActivityContext.lookupContext(getContext());
|
||||||
boolean bubbleBarEnabled = activityContext.isBubbleBarEnabled();
|
boolean bubbleBarEnabled = activityContext.isBubbleBarEnabled();
|
||||||
boolean hasBubbles = activityContext.hasBubbles();
|
boolean hasBubbles = activityContext.hasBubbles();
|
||||||
removeAllViewsInLayout();
|
|
||||||
mHasVerticalHotseat = hasVerticalHotseat;
|
mHasVerticalHotseat = hasVerticalHotseat;
|
||||||
DeviceProfile dp = mActivity.getDeviceProfile();
|
DeviceProfile dp = mActivity.getDeviceProfile();
|
||||||
|
|
||||||
|
mPagedView.resetPages(hasVerticalHotseat, mWorkspace, dp);
|
||||||
|
|
||||||
if (bubbleBarEnabled) {
|
if (bubbleBarEnabled) {
|
||||||
if (dp.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles)) {
|
for (CellLayout page : getPageLayouts()) {
|
||||||
getShortcutsAndWidgets().setTranslationProvider(
|
if (dp.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles)) {
|
||||||
cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
|
page.getShortcutsAndWidgets().setTranslationProvider(
|
||||||
if (mQsb instanceof HorizontalInsettableView) {
|
cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
|
||||||
HorizontalInsettableView insettableQsb = (HorizontalInsettableView) mQsb;
|
} else {
|
||||||
|
page.getShortcutsAndWidgets().setTranslationProvider(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mQsb instanceof HorizontalInsettableView) {
|
||||||
|
HorizontalInsettableView insettableQsb = (HorizontalInsettableView) mQsb;
|
||||||
|
if (dp.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles)) {
|
||||||
final float insetFraction = (float) dp.iconSizePx / dp.hotseatQsbWidth;
|
final float insetFraction = (float) dp.iconSizePx / dp.hotseatQsbWidth;
|
||||||
// post this to the looper so that QSB has a chance to redraw itself, e.g.
|
|
||||||
// after device rotation
|
|
||||||
mQsb.post(() -> insettableQsb.setHorizontalInsets(insetFraction));
|
mQsb.post(() -> insettableQsb.setHorizontalInsets(insetFraction));
|
||||||
}
|
} else {
|
||||||
} else {
|
insettableQsb.setHorizontalInsets(0);
|
||||||
getShortcutsAndWidgets().setTranslationProvider(null);
|
|
||||||
if (mQsb instanceof HorizontalInsettableView) {
|
|
||||||
((HorizontalInsettableView) mQsb).setHorizontalInsets(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
resetCellSize(dp);
|
|
||||||
if (hasVerticalHotseat) {
|
|
||||||
setGridSize(1, dp.numShownHotseatIcons);
|
|
||||||
} else {
|
|
||||||
setGridSize(dp.numShownHotseatIcons, dp.numHotseatRows);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjust the hotseat icons for the bubble bar.
|
* Adjust the hotseat icons for the bubble bar.
|
||||||
*
|
|
||||||
* <p>When the bubble bar becomes visible, if needed, this method animates the hotseat icons
|
|
||||||
* to reduce the spacing between them and make room for the bubble bar. The QSB width is
|
|
||||||
* animated as well to align with the hotseat icons.
|
|
||||||
*
|
|
||||||
* <p>When the bubble bar goes away, any adjustments that were previously made are reversed.
|
|
||||||
*/
|
*/
|
||||||
public void adjustForBubbleBar(boolean isBubbleBarVisible) {
|
public void adjustForBubbleBar(boolean isBubbleBarVisible) {
|
||||||
DeviceProfile dp = mActivity.getDeviceProfile();
|
DeviceProfile dp = mActivity.getDeviceProfile();
|
||||||
@@ -274,30 +349,30 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
&& dp.shouldAdjustHotseatOrQsbForBubbleBar(getContext());
|
&& dp.shouldAdjustHotseatOrQsbForBubbleBar(getContext());
|
||||||
boolean shouldAdjustHotseat = shouldAdjust
|
boolean shouldAdjustHotseat = shouldAdjust
|
||||||
&& dp.shouldAlignBubbleBarWithHotseat();
|
&& dp.shouldAlignBubbleBarWithHotseat();
|
||||||
ShortcutAndWidgetContainer icons = getShortcutsAndWidgets();
|
|
||||||
// update the translation provider for future layout passes of hotseat icons.
|
|
||||||
if (shouldAdjustHotseat) {
|
|
||||||
icons.setTranslationProvider(
|
|
||||||
cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
|
|
||||||
} else {
|
|
||||||
icons.setTranslationProvider(null);
|
|
||||||
}
|
|
||||||
AnimatorSet animatorSet = new AnimatorSet();
|
AnimatorSet animatorSet = new AnimatorSet();
|
||||||
for (int i = 0; i < icons.getChildCount(); i++) {
|
for (CellLayout page : getPageLayouts()) {
|
||||||
View child = icons.getChildAt(i);
|
ShortcutAndWidgetContainer icons = page.getShortcutsAndWidgets();
|
||||||
if (child.getLayoutParams() instanceof CellLayoutLayoutParams lp) {
|
if (shouldAdjustHotseat) {
|
||||||
float tx = shouldAdjustHotseat
|
icons.setTranslationProvider(
|
||||||
? dp.getHotseatAdjustedTranslation(getContext(), lp.getCellX()) : 0;
|
cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
|
||||||
if (child instanceof Reorderable) {
|
} else {
|
||||||
MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
|
icons.setTranslationProvider(null);
|
||||||
animatorSet.play(
|
}
|
||||||
mtd.getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM).animateToValue(tx));
|
for (int i = 0; i < icons.getChildCount(); i++) {
|
||||||
} else {
|
View child = icons.getChildAt(i);
|
||||||
animatorSet.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, tx));
|
if (child.getLayoutParams() instanceof CellLayoutLayoutParams lp) {
|
||||||
|
float tx = shouldAdjustHotseat
|
||||||
|
? dp.getHotseatAdjustedTranslation(getContext(), lp.getCellX()) : 0;
|
||||||
|
if (child instanceof Reorderable) {
|
||||||
|
MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
|
||||||
|
animatorSet.play(
|
||||||
|
mtd.getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM).animateToValue(tx));
|
||||||
|
} else {
|
||||||
|
animatorSet.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, tx));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO(b/381109832) refactor & simplify adjustment logic
|
|
||||||
boolean shouldAdjustQsb =
|
boolean shouldAdjustQsb =
|
||||||
shouldAdjustHotseat || (shouldAdjust && dp.shouldAlignBubbleBarWithQSB());
|
shouldAdjustHotseat || (shouldAdjust && dp.shouldAlignBubbleBarWithQSB());
|
||||||
if (mQsb instanceof HorizontalInsettableView horizontalInsettableQsb) {
|
if (mQsb instanceof HorizontalInsettableView horizontalInsettableQsb) {
|
||||||
@@ -315,13 +390,6 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
animatorSet.setDuration(BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS).start();
|
animatorSet.setDuration(BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getTranslationXForCell(int cellX, int cellY) {
|
|
||||||
TranslationProvider translationProvider = getShortcutsAndWidgets().getTranslationProvider();
|
|
||||||
if (translationProvider == null) return 0;
|
|
||||||
return (int) translationProvider.getTranslationX(cellX);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setInsets(Rect insets) {
|
public void setInsets(Rect insets) {
|
||||||
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
|
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
|
||||||
@@ -329,6 +397,7 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
|
|
||||||
if (grid.isVerticalBarLayout()) {
|
if (grid.isVerticalBarLayout()) {
|
||||||
mQsb.setVisibility(View.GONE);
|
mQsb.setVisibility(View.GONE);
|
||||||
|
mPageIndicator.setVisibility(GONE);
|
||||||
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
|
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||||
if (grid.isSeascape()) {
|
if (grid.isSeascape()) {
|
||||||
lp.gravity = Gravity.LEFT;
|
lp.gravity = Gravity.LEFT;
|
||||||
@@ -352,16 +421,48 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
|
|
||||||
public void setWorkspace(Workspace<?> w) {
|
public void setWorkspace(Workspace<?> w) {
|
||||||
mWorkspace = w;
|
mWorkspace = w;
|
||||||
setCellLayoutContainer(w);
|
for (CellLayout page : getPageLayouts()) {
|
||||||
|
page.setCellLayoutContainer(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTouchOnQsb(MotionEvent ev) {
|
||||||
|
if (mQsb == null || mQsb.getVisibility() != VISIBLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
float x = ev.getX();
|
||||||
|
float y = ev.getY();
|
||||||
|
return x >= mQsb.getLeft() && x < mQsb.getRight()
|
||||||
|
&& y >= mQsb.getTop() && y < mQsb.getBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MotionEvent obtainPagerEvent(MotionEvent ev) {
|
||||||
|
MotionEvent pagerEv = MotionEvent.obtain(ev);
|
||||||
|
pagerEv.offsetLocation(-mIconsContainer.getLeft() - mPagedView.getLeft(),
|
||||||
|
-mIconsContainer.getTop() - mPagedView.getTop());
|
||||||
|
return pagerEv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||||
// We allow horizontal workspace scrolling from within the Hotseat. We do this by delegating
|
|
||||||
// touch intercept the Workspace, and if it intercepts, delegating touch to the Workspace
|
|
||||||
// for the remainder of the this input stream.
|
|
||||||
int yThreshold = getMeasuredHeight() - getPaddingBottom();
|
int yThreshold = getMeasuredHeight() - getPaddingBottom();
|
||||||
if (mWorkspace != null && ev.getY() <= yThreshold) {
|
if (ev.getY() > yThreshold || isTouchOnQsb(ev)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multi-page dock: own the icon-band stream and dispatch it to the pager so empty
|
||||||
|
// pages can still scroll (same pattern as single-page workspace forwarding).
|
||||||
|
if (mPagedView.isPagingEnabled()) {
|
||||||
|
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
|
||||||
|
if (action == MotionEvent.ACTION_DOWN) {
|
||||||
|
mSendTouchToPager = true;
|
||||||
|
mSendTouchToWorkspace = false;
|
||||||
|
}
|
||||||
|
return mSendTouchToPager;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single-page dock: forward horizontal swipes to workspace.
|
||||||
|
if (mWorkspace != null) {
|
||||||
mSendTouchToWorkspace = mWorkspace.onInterceptTouchEvent(ev);
|
mSendTouchToWorkspace = mWorkspace.onInterceptTouchEvent(ev);
|
||||||
return mSendTouchToWorkspace;
|
return mSendTouchToWorkspace;
|
||||||
}
|
}
|
||||||
@@ -370,7 +471,16 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
// See comment in #onInterceptTouchEvent
|
if (mSendTouchToPager) {
|
||||||
|
MotionEvent pagerEv = obtainPagerEvent(event);
|
||||||
|
boolean handled = mPagedView.dispatchTouchEvent(pagerEv);
|
||||||
|
pagerEv.recycle();
|
||||||
|
final int action = event.getAction() & MotionEvent.ACTION_MASK;
|
||||||
|
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||||
|
mSendTouchToPager = false;
|
||||||
|
}
|
||||||
|
return handled;
|
||||||
|
}
|
||||||
if (mSendTouchToWorkspace) {
|
if (mSendTouchToWorkspace) {
|
||||||
final int action = event.getAction();
|
final int action = event.getAction();
|
||||||
switch (action & MotionEvent.ACTION_MASK) {
|
switch (action & MotionEvent.ACTION_MASK) {
|
||||||
@@ -380,45 +490,93 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
}
|
}
|
||||||
return mWorkspace.onTouchEvent(event);
|
return mWorkspace.onTouchEvent(event);
|
||||||
}
|
}
|
||||||
// Always let touch follow through to Workspace.
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||||
|
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||||
|
setMeasuredDimension(widthSize, heightSize);
|
||||||
|
|
||||||
|
int paddingLeft = getPaddingLeft();
|
||||||
|
int paddingRight = getPaddingRight();
|
||||||
|
int paddingTop = getPaddingTop();
|
||||||
|
int paddingBottom = getPaddingBottom();
|
||||||
|
|
||||||
DeviceProfile dp = mActivity.getDeviceProfile();
|
DeviceProfile dp = mActivity.getDeviceProfile();
|
||||||
|
boolean showIndicator = mPagedView.isPagingEnabled() && !dp.isVerticalBarLayout();
|
||||||
|
int indicatorSpace = showIndicator ? mPageIndicatorHeight : 0;
|
||||||
|
|
||||||
|
int pagerWidth = widthSize - paddingLeft - paddingRight;
|
||||||
|
int pagerHeight = heightSize - paddingTop - paddingBottom;
|
||||||
|
mIconsContainer.measure(
|
||||||
|
makeMeasureSpec(Math.max(0, pagerWidth), MeasureSpec.EXACTLY),
|
||||||
|
makeMeasureSpec(Math.max(0, pagerHeight), MeasureSpec.EXACTLY));
|
||||||
|
|
||||||
|
int pageHeight = Math.max(0, pagerHeight - indicatorSpace);
|
||||||
|
mPagedView.measure(
|
||||||
|
makeMeasureSpec(Math.max(0, pagerWidth), MeasureSpec.EXACTLY),
|
||||||
|
makeMeasureSpec(pageHeight, MeasureSpec.EXACTLY));
|
||||||
|
|
||||||
|
if (showIndicator) {
|
||||||
|
mPageIndicator.measure(
|
||||||
|
makeMeasureSpec(Math.max(0, pagerWidth), MeasureSpec.EXACTLY),
|
||||||
|
makeMeasureSpec(mPageIndicatorHeight, MeasureSpec.EXACTLY));
|
||||||
|
}
|
||||||
|
|
||||||
// LC: Fix weird sizing with hotseatQsbWidth being 0 on phone
|
|
||||||
int width;
|
int width;
|
||||||
if (dp.isQsbInline) {
|
if (dp.isQsbInline) {
|
||||||
width = dp.hotseatQsbWidth;
|
width = dp.hotseatQsbWidth;
|
||||||
} else {
|
} else {
|
||||||
width = getShortcutsAndWidgets().getMeasuredWidth();
|
width = mPagedView.getMeasuredWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
mQsb.measure(makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
mQsb.measure(makeMeasureSpec(Math.max(0, width), MeasureSpec.EXACTLY),
|
||||||
makeMeasureSpec(dp.getHotseatProfile().getQsbHeight(), MeasureSpec.EXACTLY));
|
makeMeasureSpec(dp.getHotseatProfile().getQsbHeight(), MeasureSpec.EXACTLY));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||||
super.onLayout(changed, l, t, r, b);
|
int width = r - l;
|
||||||
|
int height = b - t;
|
||||||
|
int paddingLeft = getPaddingLeft();
|
||||||
|
int paddingRight = getPaddingRight();
|
||||||
|
int paddingTop = getPaddingTop();
|
||||||
|
int paddingBottom = getPaddingBottom();
|
||||||
|
|
||||||
|
DeviceProfile dp = mActivity.getDeviceProfile();
|
||||||
|
mIconsContainer.layout(paddingLeft, paddingTop, width - paddingRight,
|
||||||
|
height - paddingBottom);
|
||||||
|
|
||||||
|
int containerWidth = mIconsContainer.getWidth();
|
||||||
|
int containerHeight = mIconsContainer.getHeight();
|
||||||
|
boolean showIndicator = mPagedView.isPagingEnabled() && !dp.isVerticalBarLayout();
|
||||||
|
int indicatorSpace = showIndicator ? mPageIndicatorHeight : 0;
|
||||||
|
|
||||||
|
mPagedView.layout(0, 0, containerWidth, Math.max(0, containerHeight - indicatorSpace));
|
||||||
|
|
||||||
|
if (showIndicator) {
|
||||||
|
int indicatorTop = containerHeight - mPageIndicatorHeight;
|
||||||
|
mPageIndicator.layout(0, indicatorTop, containerWidth,
|
||||||
|
indicatorTop + mPageIndicatorHeight);
|
||||||
|
mPageIndicator.setVisibility(VISIBLE);
|
||||||
|
} else {
|
||||||
|
mPageIndicator.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
|
||||||
int qsbMeasuredWidth = mQsb.getMeasuredWidth();
|
int qsbMeasuredWidth = mQsb.getMeasuredWidth();
|
||||||
int left;
|
int left;
|
||||||
DeviceProfile dp = mActivity.getDeviceProfile();
|
|
||||||
if (dp.isQsbInline) {
|
if (dp.isQsbInline) {
|
||||||
int qsbSpace = dp.hotseatBorderSpace;
|
int qsbSpace = dp.hotseatBorderSpace;
|
||||||
left = Utilities.isRtl(getResources()) ? r - getPaddingRight() + qsbSpace
|
left = Utilities.isRtl(getResources()) ? r - getPaddingRight() + qsbSpace
|
||||||
: l + getPaddingLeft() - qsbMeasuredWidth - qsbSpace;
|
: l + getPaddingLeft() - qsbMeasuredWidth - qsbSpace;
|
||||||
} else {
|
} else {
|
||||||
left = (r - l - qsbMeasuredWidth) / 2;
|
left = (width - qsbMeasuredWidth) / 2;
|
||||||
}
|
}
|
||||||
int right = left + qsbMeasuredWidth;
|
int right = left + qsbMeasuredWidth;
|
||||||
|
|
||||||
int bottom = b - t - dp.getQsbOffsetY();
|
int bottom = height - dp.getQsbOffsetY();
|
||||||
int top = bottom - dp.getHotseatProfile().getQsbHeight();
|
int top = bottom - dp.getHotseatProfile().getQsbHeight();
|
||||||
mQsb.layout(left, top, right, bottom);
|
mQsb.layout(left, top, right, bottom);
|
||||||
}
|
}
|
||||||
@@ -454,9 +612,25 @@ public class Hotseat extends CellLayout implements Insettable {
|
|||||||
return mQsb;
|
return mQsb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Delegates to the current page's shortcuts container. */
|
||||||
|
@Nullable
|
||||||
|
public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
|
||||||
|
CellLayout page = getCurrentPageLayout();
|
||||||
|
return page != null ? page.getShortcutsAndWidgets() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Delegates accessibility drag helper to the current page. */
|
||||||
|
@Nullable
|
||||||
|
public DragAndDropAccessibilityDelegate getDragAndDropAccessibilityDelegate() {
|
||||||
|
CellLayout page = getCurrentPageLayout();
|
||||||
|
return page != null ? page.getDragAndDropAccessibilityDelegate() : null;
|
||||||
|
}
|
||||||
|
|
||||||
/** Dumps the Hotseat internal state */
|
/** Dumps the Hotseat internal state */
|
||||||
public void dump(String prefix, PrintWriter writer) {
|
public void dump(String prefix, PrintWriter writer) {
|
||||||
writer.println(prefix + "Hotseat:");
|
writer.println(prefix + "Hotseat:");
|
||||||
|
writer.println(prefix + "\tpages: " + mPagedView.getPageCount()
|
||||||
|
+ " pagingEnabled=" + mPagedView.isPagingEnabled());
|
||||||
mIconsAlphaChannels.dump(
|
mIconsAlphaChannels.dump(
|
||||||
prefix + "\t",
|
prefix + "\t",
|
||||||
writer,
|
writer,
|
||||||
|
|||||||
@@ -748,7 +748,8 @@ public class Launcher extends StatefulActivity<LauncherState>
|
|||||||
mCellPosMapper = new TwoPanelCellPosMapper(mDeviceProfile.inv.numColumns);
|
mCellPosMapper = new TwoPanelCellPosMapper(mDeviceProfile.inv.numColumns);
|
||||||
} else {
|
} else {
|
||||||
mCellPosMapper = new CellPosMapper(mDeviceProfile.isVerticalBarLayout(),
|
mCellPosMapper = new CellPosMapper(mDeviceProfile.isVerticalBarLayout(),
|
||||||
mDeviceProfile.numShownHotseatIcons);
|
mDeviceProfile.numShownHotseatIcons, mDeviceProfile.numHotseatRows,
|
||||||
|
mDeviceProfile.numHotseatPages);
|
||||||
}
|
}
|
||||||
mModelWriter = mModel.getWriter(true, mCellPosMapper, this);
|
mModelWriter = mModel.getWriter(true, mCellPosMapper, this);
|
||||||
updateFixedLandscape();
|
updateFixedLandscape();
|
||||||
@@ -2151,7 +2152,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
|||||||
|
|
||||||
boolean isHotseatLayout(View layout) {
|
boolean isHotseatLayout(View layout) {
|
||||||
// TODO: Remove this method
|
// TODO: Remove this method
|
||||||
return mHotseat != null && (layout == mHotseat);
|
return mHotseat != null && (layout == mHotseat || mHotseat.isHotseatPage(layout));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -2371,7 +2372,13 @@ public class Launcher extends StatefulActivity<LauncherState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<CellLayout> containers = new ArrayList<>(mWorkspace.getPanelCount() + 1);
|
List<CellLayout> containers = new ArrayList<>(mWorkspace.getPanelCount() + 1);
|
||||||
containers.add(mWorkspace.getHotseat());
|
Hotseat hotseat = mWorkspace.getHotseat();
|
||||||
|
if (hotseat != null) {
|
||||||
|
CellLayout currentPage = hotseat.getCurrentPageLayout();
|
||||||
|
if (currentPage != null) {
|
||||||
|
containers.add(currentPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
mWorkspace.forEachVisiblePage(page -> containers.add((CellLayout) page));
|
mWorkspace.forEachVisiblePage(page -> containers.add((CellLayout) page));
|
||||||
CellLayout[] containerArray = containers.toArray(new CellLayout[0]);
|
CellLayout[] containerArray = containers.toArray(new CellLayout[0]);
|
||||||
LauncherBindableItemsContainer visibleContainer =
|
LauncherBindableItemsContainer visibleContainer =
|
||||||
@@ -2501,11 +2508,18 @@ public class Launcher extends StatefulActivity<LauncherState>
|
|||||||
|
|
||||||
writer.println(prefix + " Hotseat");
|
writer.println(prefix + " Hotseat");
|
||||||
mHotseat.dump(prefix, writer);
|
mHotseat.dump(prefix, writer);
|
||||||
ViewGroup layout = mHotseat.getShortcutsAndWidgets();
|
CellLayout[] hotseatPages = mHotseat.getPageLayouts();
|
||||||
for (int j = 0; j < layout.getChildCount(); j++) {
|
for (int p = 0; p < hotseatPages.length; p++) {
|
||||||
Object tag = layout.getChildAt(j).getTag();
|
ViewGroup layout = hotseatPages[p].getShortcutsAndWidgets();
|
||||||
if (tag != null) {
|
if (layout == null) {
|
||||||
writer.println(prefix + " " + tag);
|
continue;
|
||||||
|
}
|
||||||
|
writer.println(prefix + " Dock page " + p);
|
||||||
|
for (int j = 0; j < layout.getChildCount(); j++) {
|
||||||
|
Object tag = layout.getChildAt(j).getTag();
|
||||||
|
if (tag != null) {
|
||||||
|
writer.println(prefix + " " + tag);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2928,8 +2942,15 @@ public class Launcher extends StatefulActivity<LauncherState>
|
|||||||
* @param screenId must be presenterPos and not modelPos.
|
* @param screenId must be presenterPos and not modelPos.
|
||||||
*/
|
*/
|
||||||
public CellLayout getCellLayout(int container, int screenId) {
|
public CellLayout getCellLayout(int container, int screenId) {
|
||||||
return (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
|
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|
||||||
? mHotseat : mWorkspace.getScreenWithId(screenId);
|
|| container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
|
||||||
|
if (mHotseat == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
CellLayout page = mHotseat.getPageAt(screenId);
|
||||||
|
return page != null ? page : mHotseat.getCurrentPageLayout();
|
||||||
|
}
|
||||||
|
return mWorkspace.getScreenWithId(screenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ import java.util.function.Consumer;
|
|||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import app.lawnchair.hotseat.HotseatPagedView;
|
||||||
import app.lawnchair.preferences2.PreferenceCacheExtensionsKt;
|
import app.lawnchair.preferences2.PreferenceCacheExtensionsKt;
|
||||||
import static app.lawnchair.util.LawnchairUtilsKt.toBitmap;
|
import static app.lawnchair.util.LawnchairUtilsKt.toBitmap;
|
||||||
import app.lawnchair.LawnchairApp;
|
import app.lawnchair.LawnchairApp;
|
||||||
@@ -765,7 +766,8 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
|||||||
int dragSourceChildCount = mDragSourceInternal.getChildCount();
|
int dragSourceChildCount = mDragSourceInternal.getChildCount();
|
||||||
|
|
||||||
// If the icon was dragged from Hotseat, there is no page pair
|
// If the icon was dragged from Hotseat, there is no page pair
|
||||||
if (isTwoPanelEnabled() && !(mDragSourceInternal.getParent() instanceof Hotseat)) {
|
if (isTwoPanelEnabled() && !mLauncher.isHotseatLayout(
|
||||||
|
(View) mDragSourceInternal.getParent())) {
|
||||||
int pagePairScreenId = getScreenPair(getCellPosMapper().mapModelToPresenter(
|
int pagePairScreenId = getScreenPair(getCellPosMapper().mapModelToPresenter(
|
||||||
dragObject.dragInfo).screenId);
|
dragObject.dragInfo).screenId);
|
||||||
CellLayout pagePair = mWorkspaceScreens.get(pagePairScreenId);
|
CellLayout pagePair = mWorkspaceScreens.get(pagePairScreenId);
|
||||||
@@ -1017,6 +1019,10 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
|||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
return mWorkspaceScreens.keyAt(index);
|
return mWorkspaceScreens.keyAt(index);
|
||||||
}
|
}
|
||||||
|
Hotseat hotseat = mLauncher.getHotseat();
|
||||||
|
if (hotseat != null && hotseat.isHotseatPage(layout)) {
|
||||||
|
return layout.getHotseatPageIndex();
|
||||||
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1859,7 +1865,12 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
|||||||
protected void enableAccessibleDrag(boolean enable,
|
protected void enableAccessibleDrag(boolean enable,
|
||||||
@Nullable DragObject dragObject) {
|
@Nullable DragObject dragObject) {
|
||||||
super.enableAccessibleDrag(enable, dragObject);
|
super.enableAccessibleDrag(enable, dragObject);
|
||||||
setEnableForLayout(mLauncher.getHotseat(), enable);
|
Hotseat hotseat = mLauncher.getHotseat();
|
||||||
|
if (hotseat != null) {
|
||||||
|
for (CellLayout page : hotseat.getPageLayouts()) {
|
||||||
|
setEnableForLayout(page, enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (enable && dragObject != null
|
if (enable && dragObject != null
|
||||||
&& dragObject.dragInfo instanceof LauncherAppWidgetInfo) {
|
&& dragObject.dragInfo instanceof LauncherAppWidgetInfo) {
|
||||||
mLauncher.getHotseat().setImportantForAccessibility(
|
mLauncher.getHotseat().setImportantForAccessibility(
|
||||||
@@ -2787,7 +2798,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
|||||||
private boolean setDropLayoutForDragObject(DragObject d, float centerX, float centerY) {
|
private boolean setDropLayoutForDragObject(DragObject d, float centerX, float centerY) {
|
||||||
CellLayout layout = null;
|
CellLayout layout = null;
|
||||||
if (shouldUseHotseatAsDropLayout(d)) {
|
if (shouldUseHotseatAsDropLayout(d)) {
|
||||||
layout = mLauncher.getHotseat();
|
layout = resolveHotseatDropLayout(d);
|
||||||
} else if (!isDragObjectOverSmartSpace(d)) {
|
} else if (!isDragObjectOverSmartSpace(d)) {
|
||||||
// If the object is over qsb/smartspace, we don't want to highlight anything.
|
// If the object is over qsb/smartspace, we don't want to highlight anything.
|
||||||
|
|
||||||
@@ -2813,14 +2824,35 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private CellLayout resolveHotseatDropLayout(DragObject dragObject) {
|
||||||
|
Hotseat hotseat = mLauncher.getHotseat();
|
||||||
|
if (hotseat == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Prefer the visible dock page so drops land where the user is looking.
|
||||||
|
CellLayout current = hotseat.getCurrentPageLayout();
|
||||||
|
HotseatPagedView pager = hotseat.getPagedView();
|
||||||
|
if (pager != null && pager.isPageInTransition()) {
|
||||||
|
float[] xy = new float[] { dragObject.x, dragObject.y };
|
||||||
|
mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, xy, true);
|
||||||
|
mLauncher.getDragLayer().mapCoordInSelfToDescendant(pager, xy);
|
||||||
|
CellLayout underFinger = pager.findPageAtLocalX(xy[0]);
|
||||||
|
if (underFinger != null) {
|
||||||
|
return underFinger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean shouldUseHotseatAsDropLayout(DragObject dragObject) {
|
private boolean shouldUseHotseatAsDropLayout(DragObject dragObject) {
|
||||||
if (mLauncher.getHotseat() == null
|
if (mLauncher.getHotseat() == null
|
||||||
|| mLauncher.getHotseat().getShortcutsAndWidgets() == null
|
|| mLauncher.getHotseat().getShortcutsAndWidgets() == null
|
||||||
|| isDragWidget(dragObject)) {
|
|| isDragWidget(dragObject)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
View hotseatShortcuts = mLauncher.getHotseat().getShortcutsAndWidgets();
|
View hotseatIcons = mLauncher.getHotseat().getPagedView();
|
||||||
getViewBoundsRelativeToWorkspace(hotseatShortcuts, mTempRect);
|
getViewBoundsRelativeToWorkspace(hotseatIcons, mTempRect);
|
||||||
return mTempRect.contains(dragObject.x, dragObject.y);
|
return mTempRect.contains(dragObject.x, dragObject.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3710,16 +3742,14 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
|||||||
*/
|
*/
|
||||||
private CellLayout[] getWorkspaceAndHotseatCellLayouts() {
|
private CellLayout[] getWorkspaceAndHotseatCellLayouts() {
|
||||||
int screenCount = getChildCount();
|
int screenCount = getChildCount();
|
||||||
final CellLayout[] layouts;
|
CellLayout[] hotseatPages = mLauncher.getHotseat() != null
|
||||||
if (mLauncher.getHotseat() != null) {
|
? mLauncher.getHotseat().getPageLayouts()
|
||||||
layouts = new CellLayout[screenCount + 1];
|
: new CellLayout[0];
|
||||||
layouts[screenCount] = mLauncher.getHotseat();
|
final CellLayout[] layouts = new CellLayout[screenCount + hotseatPages.length];
|
||||||
} else {
|
|
||||||
layouts = new CellLayout[screenCount];
|
|
||||||
}
|
|
||||||
for (int screen = 0; screen < screenCount; screen++) {
|
for (int screen = 0; screen < screenCount; screen++) {
|
||||||
layouts[screen] = (CellLayout) getChildAt(screen);
|
layouts[screen] = (CellLayout) getChildAt(screen);
|
||||||
}
|
}
|
||||||
|
System.arraycopy(hotseatPages, 0, layouts, screenCount, hotseatPages.length);
|
||||||
return layouts;
|
return layouts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,16 +54,18 @@ public interface WorkspaceLayoutManager {
|
|||||||
CellPos presenterPos = getCellPosMapper().mapModelToPresenter(info);
|
CellPos presenterPos = getCellPosMapper().mapModelToPresenter(info);
|
||||||
int x = presenterPos.cellX;
|
int x = presenterPos.cellX;
|
||||||
int y = presenterPos.cellY;
|
int y = presenterPos.cellY;
|
||||||
|
int screenId = presenterPos.screenId;
|
||||||
if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|
if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|
||||||
|| info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
|
|| info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
|
||||||
int screenId = presenterPos.screenId;
|
// Model screenId is a flat rank; presenter screenId is the dock page.
|
||||||
x = getHotseat().getCellXFromOrder(screenId);
|
int rank = info.screenId;
|
||||||
y = getHotseat().getCellYFromOrder(screenId);
|
x = getHotseat().getCellXFromOrder(rank);
|
||||||
// TODO(b/335141365): Remove this log after the bug is fixed.
|
y = getHotseat().getCellYFromOrder(rank);
|
||||||
|
screenId = getHotseat().getPageFromOrder(rank);
|
||||||
Log.d(TAG, "addInScreenFromBind: hotseat inflation with x = " + x
|
Log.d(TAG, "addInScreenFromBind: hotseat inflation with x = " + x
|
||||||
+ " and y = " + y);
|
+ " and y = " + y + " page = " + screenId);
|
||||||
}
|
}
|
||||||
addInScreen(child, info.container, presenterPos.screenId, x, y, info.spanX, info.spanY);
|
addInScreen(child, info.container, screenId, x, y, info.spanX, info.spanY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,10 +105,18 @@ public interface WorkspaceLayoutManager {
|
|||||||
throw new RuntimeException("Screen id should not be extra empty screen: " + screenId);
|
throw new RuntimeException("Screen id should not be extra empty screen: " + screenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
final CellLayout layout;
|
CellLayout layout;
|
||||||
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|
||||||
|| container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
|
|| container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
|
||||||
layout = getHotseat();
|
Hotseat hotseat = getHotseat();
|
||||||
|
layout = hotseat.getPageAt(screenId);
|
||||||
|
if (layout == null) {
|
||||||
|
layout = hotseat.getCurrentPageLayout();
|
||||||
|
}
|
||||||
|
if (layout == null) {
|
||||||
|
Log.e(TAG, "Skipping child, hotseat page " + screenId + " not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Hide folder title in the hotseat
|
// Hide folder title in the hotseat
|
||||||
if (child instanceof FolderIcon) {
|
if (child instanceof FolderIcon) {
|
||||||
|
|||||||
@@ -12,12 +12,15 @@
|
|||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* Modifications copyright 2026 Lawnchair
|
||||||
*/
|
*/
|
||||||
package com.android.launcher3.celllayout;
|
package com.android.launcher3.celllayout;
|
||||||
|
|
||||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
||||||
|
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||||
|
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
|
||||||
|
|
||||||
import com.android.launcher3.LauncherSettings.Favorites;
|
|
||||||
import com.android.launcher3.model.data.ItemInfo;
|
import com.android.launcher3.model.data.ItemInfo;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -27,19 +30,42 @@ import java.util.Objects;
|
|||||||
*/
|
*/
|
||||||
public class CellPosMapper {
|
public class CellPosMapper {
|
||||||
|
|
||||||
public static final CellPosMapper DEFAULT = new CellPosMapper(false, -1);
|
public static final CellPosMapper DEFAULT = new CellPosMapper(false, -1, 1, 1);
|
||||||
private final boolean mHasVerticalHotseat;
|
private final boolean mHasVerticalHotseat;
|
||||||
private final int mNumOfHotseat;
|
private final int mNumOfHotseat;
|
||||||
|
private final int mNumHotseatRows;
|
||||||
|
|
||||||
public CellPosMapper(boolean hasVerticalHotseat, int numOfHotseat) {
|
public CellPosMapper(boolean hasVerticalHotseat, int numOfHotseat) {
|
||||||
|
this(hasVerticalHotseat, numOfHotseat, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellPosMapper(boolean hasVerticalHotseat, int numOfHotseat, int numHotseatRows,
|
||||||
|
int numHotseatPages) {
|
||||||
mHasVerticalHotseat = hasVerticalHotseat;
|
mHasVerticalHotseat = hasVerticalHotseat;
|
||||||
mNumOfHotseat = numOfHotseat;
|
mNumOfHotseat = numOfHotseat;
|
||||||
|
mNumHotseatRows = Math.max(1, numHotseatRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isHotseatContainer(int container) {
|
||||||
|
return container == CONTAINER_HOTSEAT || container == CONTAINER_HOTSEAT_PREDICTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps the position in model to the position in view
|
* Maps the position in model to the position in view.
|
||||||
|
* Hotseat model screenId is a flat rank; presenter screenId is the dock page index.
|
||||||
*/
|
*/
|
||||||
public CellPos mapModelToPresenter(ItemInfo info) {
|
public CellPos mapModelToPresenter(ItemInfo info) {
|
||||||
|
if (isHotseatContainer(info.container) && mNumOfHotseat > 0) {
|
||||||
|
if (mHasVerticalHotseat) {
|
||||||
|
return new CellPos(0, mNumOfHotseat - info.screenId - 1, 0);
|
||||||
|
}
|
||||||
|
int slotsPerPage = Math.max(1, mNumOfHotseat * mNumHotseatRows);
|
||||||
|
int page = info.screenId / slotsPerPage;
|
||||||
|
int local = info.screenId % slotsPerPage;
|
||||||
|
int cellX = local % mNumOfHotseat;
|
||||||
|
int cellY = local / mNumOfHotseat;
|
||||||
|
return new CellPos(cellX, cellY, page);
|
||||||
|
}
|
||||||
return new CellPos(info.cellX, info.cellY, info.screenId);
|
return new CellPos(info.cellX, info.cellY, info.screenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,12 +74,17 @@ public class CellPosMapper {
|
|||||||
*/
|
*/
|
||||||
public CellPos mapPresenterToModel(int presenterX, int presenterY, int presenterScreen,
|
public CellPos mapPresenterToModel(int presenterX, int presenterY, int presenterScreen,
|
||||||
int container) {
|
int container) {
|
||||||
if (container == Favorites.CONTAINER_HOTSEAT) {
|
if (isHotseatContainer(container)) {
|
||||||
if (mHasVerticalHotseat) {
|
if (mHasVerticalHotseat && mNumOfHotseat > 0) {
|
||||||
presenterScreen = mNumOfHotseat - presenterY - 1;
|
presenterScreen = mNumOfHotseat - presenterY - 1;
|
||||||
} else {
|
} else if (!mHasVerticalHotseat && mNumOfHotseat > 0) {
|
||||||
// For multi-row hotseat, screenId (rank) = row * numColumns + column
|
// presenterScreen is the dock page index (0-based)
|
||||||
presenterScreen = presenterY * mNumOfHotseat + presenterX;
|
int page = Math.max(0, presenterScreen);
|
||||||
|
int slotsPerPage = Math.max(1, mNumOfHotseat * mNumHotseatRows);
|
||||||
|
int localRank = presenterY * mNumOfHotseat + presenterX;
|
||||||
|
presenterScreen = page * slotsPerPage + localRank;
|
||||||
|
} else if (!mHasVerticalHotseat) {
|
||||||
|
presenterScreen = presenterX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new CellPos(presenterX, presenterY, presenterScreen);
|
return new CellPos(presenterX, presenterY, presenterScreen);
|
||||||
@@ -67,7 +98,7 @@ public class CellPosMapper {
|
|||||||
private final int mColumnCount;
|
private final int mColumnCount;
|
||||||
|
|
||||||
public TwoPanelCellPosMapper(int columnCount) {
|
public TwoPanelCellPosMapper(int columnCount) {
|
||||||
super(false, -1);
|
super(false, -1, 1, 1);
|
||||||
mColumnCount = columnCount;
|
mColumnCount = columnCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -183,10 +183,16 @@ public class KeyboardDragAndDropView extends AbstractFloatingView
|
|||||||
mDelegates.add(((CellLayout) pv.getChildAt(i)).getDragAndDropAccessibilityDelegate());
|
mDelegates.add(((CellLayout) pv.getChildAt(i)).getDragAndDropAccessibilityDelegate());
|
||||||
}
|
}
|
||||||
if (openFolder == null) {
|
if (openFolder == null) {
|
||||||
mDelegates.add(pv.getNextPage() + 1,
|
DragAndDropAccessibilityDelegate hotseatDelegate =
|
||||||
mLauncher.getHotseat().getDragAndDropAccessibilityDelegate());
|
mLauncher.getHotseat().getDragAndDropAccessibilityDelegate();
|
||||||
|
if (hotseatDelegate != null) {
|
||||||
|
mDelegates.add(pv.getNextPage() + 1, hotseatDelegate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mDelegates.forEach(delegate -> {
|
mDelegates.forEach(delegate -> {
|
||||||
|
if (delegate == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
mIntList.clear();
|
mIntList.clear();
|
||||||
delegate.getVisibleVirtualViews(mIntList);
|
delegate.getVisibleVirtualViews(mIntList);
|
||||||
mIntList.forEach(id -> mNodes.add(new VirtualNodeInfo(delegate, id)));
|
mIntList.forEach(id -> mNodes.add(new VirtualNodeInfo(delegate, id)));
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ import com.android.launcher3.widget.LauncherWidgetHolder;
|
|||||||
|
|
||||||
import app.lawnchair.preferences2.PreferenceCacheExtensionsKt;
|
import app.lawnchair.preferences2.PreferenceCacheExtensionsKt;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -307,7 +308,10 @@ public class LauncherPreviewRenderer extends BaseContext
|
|||||||
|
|
||||||
private List<CellLayout> getAllLayouts() {
|
private List<CellLayout> getAllLayouts() {
|
||||||
List<CellLayout> screens = new ArrayList<>(mWorkspaceScreens.values());
|
List<CellLayout> screens = new ArrayList<>(mWorkspaceScreens.values());
|
||||||
screens.add(getHotseat());
|
Hotseat hotseat = getHotseat();
|
||||||
|
if (hotseat != null) {
|
||||||
|
Collections.addAll(screens, hotseat.getPageLayouts());
|
||||||
|
}
|
||||||
return screens;
|
return screens;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,7 +383,8 @@ public class LauncherPreviewRenderer extends BaseContext
|
|||||||
|
|
||||||
int cellX = mHotseat.getCellXFromOrder(rank);
|
int cellX = mHotseat.getCellXFromOrder(rank);
|
||||||
int cellY = mHotseat.getCellYFromOrder(rank);
|
int cellY = mHotseat.getCellYFromOrder(rank);
|
||||||
if (mHotseat.isOccupied(cellX, cellY)) continue;
|
CellLayout page = mHotseat.getPageAt(mHotseat.getPageFromOrder(rank));
|
||||||
|
if (page == null || page.isOccupied(cellX, cellY)) continue;
|
||||||
|
|
||||||
WorkspaceItemInfo itemInfo =
|
WorkspaceItemInfo itemInfo =
|
||||||
new WorkspaceItemInfo((WorkspaceItemInfo) predictions.get(predictionIndex));
|
new WorkspaceItemInfo((WorkspaceItemInfo) predictions.get(predictionIndex));
|
||||||
|
|||||||
@@ -297,8 +297,12 @@ public class TestInformationHandler implements ResourceBasedOverride {
|
|||||||
int cellIndex = extra.getInt(TestProtocol.TEST_INFO_PARAM_INDEX);
|
int cellIndex = extra.getInt(TestProtocol.TEST_INFO_PARAM_INDEX);
|
||||||
return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
|
return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
|
||||||
final Hotseat hotseat = launcher.getHotseat();
|
final Hotseat hotseat = launcher.getHotseat();
|
||||||
|
final CellLayout page = hotseat.getCurrentPageLayout();
|
||||||
|
if (page == null) {
|
||||||
|
return new Point(0, 0);
|
||||||
|
}
|
||||||
final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher,
|
final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher,
|
||||||
hotseat, cellIndex, /* cellY= */ 0,
|
page, cellIndex, /* cellY= */ 0,
|
||||||
/* spanX= */ 1, /* spanY= */ 1);
|
/* spanX= */ 1, /* spanY= */ 1);
|
||||||
// TODO(b/234322284): return the real center point.
|
// TODO(b/234322284): return the real center point.
|
||||||
return new Point(cellRect.left + (cellRect.right - cellRect.left) / 3,
|
return new Point(cellRect.left + (cellRect.right - cellRect.left) / 3,
|
||||||
@@ -428,6 +432,9 @@ public class TestInformationHandler implements ResourceBasedOverride {
|
|||||||
ShortcutAndWidgetContainer hotseatIconsContainer =
|
ShortcutAndWidgetContainer hotseatIconsContainer =
|
||||||
l.getHotseat().getShortcutsAndWidgets();
|
l.getHotseat().getShortcutsAndWidgets();
|
||||||
ArrayList<String> hotseatIconNames = new ArrayList<>();
|
ArrayList<String> hotseatIconNames = new ArrayList<>();
|
||||||
|
if (hotseatIconsContainer == null) {
|
||||||
|
return hotseatIconNames;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < hotseatIconsContainer.getChildCount(); i++) {
|
for (int i = 0; i < hotseatIconsContainer.getChildCount(); i++) {
|
||||||
// Use unchecked cast to catch changes in hotseat layout
|
// Use unchecked cast to catch changes in hotseat layout
|
||||||
|
|||||||
@@ -554,7 +554,8 @@ public interface ActivityContext extends SavedStateRegistryOwner {
|
|||||||
|
|
||||||
default CellPosMapper getCellPosMapper() {
|
default CellPosMapper getCellPosMapper() {
|
||||||
DeviceProfile dp = getDeviceProfile();
|
DeviceProfile dp = getDeviceProfile();
|
||||||
return new CellPosMapper(dp.isVerticalBarLayout(), dp.numShownHotseatIcons);
|
return new CellPosMapper(dp.isVerticalBarLayout(), dp.numShownHotseatIcons,
|
||||||
|
dp.numHotseatRows, dp.numHotseatPages);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a writer for updating model properties */
|
/** Returns a writer for updating model properties */
|
||||||
|
|||||||
Reference in new issue
Block a user