Add Support for big screen (#4461)

* Initial Support for big screen

* Fixed regressions

* Don't recalculate when device isTablet
This commit is contained in:
John Andrew Camu
2024-06-22 12:58:43 +08:00
committed by GitHub
parent aaa00c19f4
commit 930d0e6cd7
34 changed files with 1983 additions and 1003 deletions

View File

@@ -32,6 +32,7 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<uses-permission android:name="android.permission.MANAGE_USERS"/>
<uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" />
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<!--override minSdk declared in it-->
<uses-sdk tools:overrideLibrary="com.kieronquinn.app.smartspacer.sdk" />

View File

@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto">
<FrameLayout
android:id="@+id/widgets_two_pane_sheet_paged_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="start"
android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
android:layout_gravity="start"
android:layout_alignParentStart="true">
<com.android.launcher3.widget.picker.WidgetPagedView
android:id="@+id/widgets_view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
launcher:pageIndicator="@+id/tabs" >
<com.android.launcher3.widget.picker.WidgetsRecyclerView
android:id="@+id/primary_widgets_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
<com.android.launcher3.widget.picker.WidgetsRecyclerView
android:id="@+id/work_widgets_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
</com.android.launcher3.widget.picker.WidgetPagedView>
<!-- SearchAndRecommendationsView without the tab layout as well -->
<com.android.launcher3.views.StickyHeaderLayout
android:id="@+id/search_and_recommendations_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToOutline="true"
android:orientation="vertical">
<FrameLayout
android:id="@+id/search_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/widgetPickerPrimarySurfaceColor"
android:clipToPadding="false"
android:elevation="0.1dp"
android:paddingBottom="8dp"
launcher:layout_sticky="true">
<include layout="@layout/widgets_search_bar" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/suggestions_header"
android:layout_marginTop="8dp"
android:orientation="horizontal"
android:background="?attr/widgetPickerPrimarySurfaceColor"
launcher:layout_sticky="true">
</LinearLayout>
<com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="64dp"
android:gravity="center_horizontal"
android:orientation="horizontal"
android:paddingVertical="8dp"
android:background="?attr/widgetPickerPrimarySurfaceColor"
style="@style/TextHeadline"
launcher:layout_sticky="true">
<Button
android:id="@+id/tab_personal"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
android:layout_weight="1"
android:background="@drawable/widget_picker_tabs_background"
android:text="@string/widgets_full_sheet_personal_tab"
android:textColor="@color/widget_picker_tab_text"
android:textSize="14sp"
style="?android:attr/borderlessButtonStyle" />
<Button
android:id="@+id/tab_work"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
android:layout_weight="1"
android:background="@drawable/widget_picker_tabs_background"
android:text="@string/widgets_full_sheet_work_tab"
android:textColor="@color/widget_picker_tab_text"
android:textSize="14sp"
style="?android:attr/borderlessButtonStyle" />
</com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
</com.android.launcher3.views.StickyHeaderLayout>
</FrameLayout>
</merge>

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2021 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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto">
<FrameLayout
android:id="@+id/widgets_two_pane_sheet_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="start"
android:layout_gravity="start"
android:layout_alignParentStart="true">
<com.android.launcher3.widget.picker.WidgetsRecyclerView
android:id="@+id/primary_widgets_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
android:clipToPadding="false" />
<!-- SearchAndRecommendationsView without the tab layout as well -->
<com.android.launcher3.views.StickyHeaderLayout
android:id="@+id/search_and_recommendations_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToOutline="true"
android:orientation="vertical">
<FrameLayout
android:id="@+id/search_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/widgetPickerPrimarySurfaceColor"
android:clipToPadding="false"
android:elevation="0.1dp"
android:paddingBottom="8dp"
android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
launcher:layout_sticky="true">
<include layout="@layout/widgets_search_bar" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/suggestions_header"
android:layout_marginTop="8dp"
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
android:paddingBottom="16dp"
android:orientation="horizontal"
android:background="?attr/widgetPickerPrimarySurfaceColor"
launcher:layout_sticky="true">
</LinearLayout>
</com.android.launcher3.views.StickyHeaderLayout>
</FrameLayout>
</merge>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 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.
-->
<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="wrap_content"
android:layout_height="wrap_content"
android:hapticFeedbackEnabled="false"
launcher:containerType="workspace" />

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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.
-->
<com.android.launcher3.MultipageCellLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hapticFeedbackEnabled="false"
launcher:containerType="workspace" />

View File

@@ -130,7 +130,7 @@
<item name="config_default_home_icon_label_folder_size_factor" type="dimen" format="float">1.0</item>
<item name="config_default_drawer_icon_label_size_factor" type="dimen" format="float">1.0</item>
<item name="config_default_drawer_cell_height_factor" type="dimen" format="float">1.0</item>
<item name="config_default_drawer_left_right_factor" type="dimen" format="float">0.4</item>
<item name="config_default_drawer_left_right_factor" type="dimen" format="float">0.0</item>
<item name="config_default_search_max_result_count" type="dimen" format="integer">5</item>
<item name="config_default_files_max_result_count" type="dimen" format="integer">3</item>
<item name="config_default_people_max_result_count" type="dimen" format="integer">10</item>

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 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.
-->
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
<!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
<!-- Dialer, Messaging, Browser, Camera -->
<resolve
launcher:container="-101"
launcher:screen="0"
launcher:x="0"
launcher:y="0" >
<favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
<favorite launcher:uri="tel:123" />
<favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
</resolve>
<resolve
launcher:container="-101"
launcher:screen="1"
launcher:x="1"
launcher:y="0" >
<favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
<favorite launcher:uri="sms:" />
<favorite launcher:uri="smsto:" />
<favorite launcher:uri="mms:" />
<favorite launcher:uri="mmsto:" />
</resolve>
<resolve
launcher:container="-101"
launcher:screen="2"
launcher:x="2"
launcher:y="0" >
<favorite
launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
<favorite launcher:uri="http://www.example.com/" />
</resolve>
<resolve
launcher:container="-101"
launcher:screen="3"
launcher:x="3"
launcher:y="0" >
<favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
<favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
</resolve>
<!-- Bottom row -->
<resolve
launcher:screen="0"
launcher:x="0"
launcher:y="-1" >
<favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
<favorite launcher:uri="mailto:" />
</resolve>
<resolve
launcher:screen="0"
launcher:x="1"
launcher:y="-1" >
<favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
<favorite launcher:uri="#Intent;type=images/*;end" />
</resolve>
<resolve
launcher:screen="0"
launcher:x="3"
launcher:y="-1" >
<favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
<favorite launcher:uri="market://details?id=com.android.launcher" />
</resolve>
</favorites>

View File

@@ -74,9 +74,8 @@ class SearchResultRightLeftIcon(context: Context, attrs: AttributeSet?) :
LayoutParams.MATCH_PARENT,
heightRes,
)
val horizontalMargin = grid.allAppsLeftRightPadding
layoutParams.leftMargin = horizontalMargin
layoutParams.rightMargin = horizontalMargin
layoutParams.leftMargin = grid.allAppsPadding.left
layoutParams.rightMargin = grid.allAppsPadding.right
this.layoutParams = layoutParams
}

View File

@@ -71,7 +71,7 @@ class LawnchairSearchAdapterProvider(
): BaseAllAppsAdapter.ViewHolder {
val view = layoutInflater.inflate(layoutIdMap[viewType], parent, false)
val grid: DeviceProfile = mLauncher.deviceProfile
val horizontalMargin = if (grid.isTablet) grid.allAppsLeftRightPadding + 48 else grid.allAppsLeftRightPadding
val horizontalMargin = if (grid.isTablet) grid.allAppsPadding.left + grid.allAppsPadding.right + 48 else grid.allAppsPadding.left + grid.allAppsPadding.right
if (viewType != SEARCH_RESULT_ICON) {
val layoutParams = ViewGroup.MarginLayoutParams(view.layoutParams)

View File

@@ -354,6 +354,7 @@
<dimen name="taskbar_edu_features_horizontal_spacing">24dp</dimen>
<dimen name="taskbar_edu_features_tooltip_width_persistent">624dp</dimen>
<dimen name="taskbar_edu_features_tooltip_width_transient">428dp</dimen>
<dimen name="task_thumbnail_icon_menu_drawable_size">24dp</dimen>
<!--- Taskbar Pinning -->
<dimen name="taskbar_pinning_popup_menu_width">300dp</dimen>
@@ -382,6 +383,7 @@
<dimen name="bubblebar_dismiss_target_icon_size">24dp</dimen>
<dimen name="bubblebar_dismiss_target_bottom_margin">50dp</dimen>
<dimen name="bubblebar_dismiss_floating_gradient_height">548dp</dimen>
<dimen name="bubblebar_hotseat_adjustment_threshold">90dp</dimen>
<!-- Launcher splash screen -->
<!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->

View File

@@ -124,13 +124,13 @@ public class AllAppsState extends LauncherState {
@Override
public int getFloatingSearchBarRestingMarginStart(Launcher launcher) {
DeviceProfile dp = launcher.getDeviceProfile();
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(launcher);
}
@Override
public int getFloatingSearchBarRestingMarginEnd(Launcher launcher) {
DeviceProfile dp = launcher.getDeviceProfile();
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(launcher);
}
@Override

View File

@@ -164,7 +164,19 @@
<!-- numFolderRows & numFolderColumns defaults to numRows & numColumns, if not specified -->
<attr name="numFolderRows" format="integer" />
<!-- defaults to numFolderRows, if not specified -->
<attr name="numFolderRowsLandscape" format="integer" />
<!-- defaults to numFolderRows, if not specified -->
<attr name="numFolderRowsTwoPanelLandscape" format="integer" />
<!-- defaults to numFolderRows, if not specified -->
<attr name="numFolderRowsTwoPanelPortrait" format="integer" />
<attr name="numFolderColumns" format="integer" />
<!-- defaults to numFolderColumns, if not specified -->
<attr name="numFolderColumnsLandscape" format="integer" />
<!-- defaults to numFolderColumns, if not specified -->
<attr name="numFolderColumnsTwoPanelLandscape" format="integer" />
<!-- defaults to numFolderColumns, if not specified -->
<attr name="numFolderColumnsTwoPanelPortrait" format="integer" />
<!-- Support attributes in FolderStyle -->
<attr name="folderStyle" format="reference" />
@@ -220,6 +232,14 @@
Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
<attr name="hotseatSpecsId" format="reference" />
<attr name="hotseatSpecsTwoPanelId" format="reference" />
<!-- File that contains the specs for workspace icon and text size.
Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
<attr name="workspaceCellSpecsId" format="reference" />
<attr name="workspaceCellSpecsTwoPanelId" format="reference" />
<!-- File that contains the specs for all apps icon and text size.
Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
<attr name="allAppsCellSpecsId" format="reference" />
<attr name="allAppsCellSpecsTwoPanelId" format="reference" />
<!-- By default all categories are enabled -->
<attr name="deviceCategory" format="integer">
@@ -261,30 +281,39 @@
<!-- Responsive grids attributes -->
<declare-styleable name="ResponsiveSpec">
<attr name="specType" format="integer">
<attr name="dimensionType" format="integer">
<enum name="height" value="0" />
<enum name="width" value="1" />
</attr>
<attr name="maxAvailableSize" format="dimension" />
</declare-styleable>
<declare-styleable name="ResponsiveSpecGroup">
<attr name="maxAspectRatio" format="float" />
</declare-styleable>
<declare-styleable name="WorkspaceSpec">
<attr name="specType" />
<attr name="dimensionType" />
<attr name="maxAvailableSize" />
</declare-styleable>
<declare-styleable name="FolderSpec">
<attr name="specType" />
<attr name="dimensionType" />
<attr name="maxAvailableSize" />
</declare-styleable>
<declare-styleable name="AllAppsSpec">
<attr name="specType" />
<attr name="dimensionType" />
<attr name="maxAvailableSize" />
</declare-styleable>
<declare-styleable name="HotseatSpec">
<attr name="specType" />
<attr name="dimensionType" />
<attr name="maxAvailableSize" />
</declare-styleable>
<declare-styleable name="CellSpec">
<attr name="dimensionType" />
<attr name="maxAvailableSize" />
</declare-styleable>

View File

@@ -225,6 +225,8 @@
<dimen name="iconSize66dp">72dp</dimen>
<dimen name="iconSize72dp">79dp</dimen>
<dimen name="minimum_icon_label_size">8sp</dimen>
<!-- Icon size steps in dp -->
<integer-array name="icon_size_steps">
<item>@dimen/iconSize48dp</item>

File diff suppressed because it is too large Load Diff

View File

@@ -211,6 +211,14 @@ public class InvariantDeviceProfile {
@XmlRes
public int hotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
@XmlRes
public int workspaceCellSpecsId = INVALID_RESOURCE_HANDLE;
@XmlRes
public int workspaceCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
@XmlRes
public int allAppsCellSpecsId = INVALID_RESOURCE_HANDLE;
@XmlRes
public int allAppsCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
public int demoModeLayoutId;
public boolean[] inlineQsb = new boolean[COUNT_SIZES];
@@ -386,7 +394,7 @@ public class InvariantDeviceProfile {
closestProfile = displayOption.grid;
numRows = dbGridInfo.getNumRows();
numColumns = dbGridInfo.getNumColumns();
numSearchContainerColumns = dbGridInfo.getNumHotseatColumns();
numSearchContainerColumns = closestProfile.numSearchContainerColumns;
dbFile = dbGridInfo.getDbFile();
defaultLayoutId = closestProfile.defaultLayoutId;
demoModeLayoutId = closestProfile.demoModeLayoutId;
@@ -407,6 +415,10 @@ public class InvariantDeviceProfile {
folderSpecsTwoPanelId = closestProfile.mFolderSpecsTwoPanelId;
hotseatSpecsId = closestProfile.mHotseatSpecsId;
hotseatSpecsTwoPanelId = closestProfile.mHotseatSpecsTwoPanelId;
workspaceCellSpecsId = closestProfile.mWorkspaceCellSpecsId;
workspaceCellSpecsTwoPanelId = closestProfile.mWorkspaceCellSpecsTwoPanelId;
allAppsCellSpecsId = closestProfile.mAllAppsCellSpecsId;
allAppsCellSpecsTwoPanelId = closestProfile.mAllAppsCellSpecsTwoPanelId;
this.deviceType = deviceType;
@@ -428,7 +440,8 @@ public class InvariantDeviceProfile {
horizontalMargin = displayOption.horizontalMargin;
numShownHotseatIcons = numSearchContainerColumns;
numShownHotseatIcons = deviceType == TYPE_MULTI_DISPLAY
? closestProfile.numHotseatIcons : dbGridInfo.getNumHotseatColumns();
numDatabaseHotseatIcons = deviceType == TYPE_MULTI_DISPLAY
? closestProfile.numDatabaseHotseatIcons : numShownHotseatIcons;
hotseatColumnSpan = closestProfile.hotseatColumnSpan;
@@ -500,9 +513,6 @@ public class InvariantDeviceProfile {
deviceProfile.numShownHotseatIcons = numMinShownHotseatIconsForTablet;
deviceProfile.recalculateHotseatWidthAndBorderSpace();
});
ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName());
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
}
public void addOnChangeListener(OnIDPChangeListener listener) {
@@ -870,6 +880,10 @@ public class InvariantDeviceProfile {
private final int mFolderSpecsTwoPanelId;
private final int mHotseatSpecsId;
private final int mHotseatSpecsTwoPanelId;
private final int mWorkspaceCellSpecsId;
private final int mWorkspaceCellSpecsTwoPanelId;
private final int mAllAppsCellSpecsId;
private final int mAllAppsCellSpecsTwoPanelId;
private final boolean isScalable;
private final int devicePaddingId;
@@ -955,6 +969,18 @@ public class InvariantDeviceProfile {
mHotseatSpecsTwoPanelId = a.getResourceId(
R.styleable.GridDisplayOption_hotseatSpecsTwoPanelId,
INVALID_RESOURCE_HANDLE);
mWorkspaceCellSpecsId = a.getResourceId(
R.styleable.GridDisplayOption_workspaceCellSpecsId,
INVALID_RESOURCE_HANDLE);
mWorkspaceCellSpecsTwoPanelId = a.getResourceId(
R.styleable.GridDisplayOption_workspaceCellSpecsTwoPanelId,
INVALID_RESOURCE_HANDLE);
mAllAppsCellSpecsId = a.getResourceId(
R.styleable.GridDisplayOption_allAppsCellSpecsId,
INVALID_RESOURCE_HANDLE);
mAllAppsCellSpecsTwoPanelId = a.getResourceId(
R.styleable.GridDisplayOption_allAppsCellSpecsTwoPanelId,
INVALID_RESOURCE_HANDLE);
} else {
mWorkspaceSpecsId = INVALID_RESOURCE_HANDLE;
mWorkspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
@@ -964,6 +990,10 @@ public class InvariantDeviceProfile {
mFolderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
mHotseatSpecsId = INVALID_RESOURCE_HANDLE;
mHotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
mWorkspaceCellSpecsId = INVALID_RESOURCE_HANDLE;
mWorkspaceCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
mAllAppsCellSpecsId = INVALID_RESOURCE_HANDLE;
mAllAppsCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
}
int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,

View File

@@ -846,7 +846,7 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
*/
public int getFloatingSearchBarRestingMarginStart() {
DeviceProfile dp = mActivityContext.getDeviceProfile();
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(mContext);
}
/**
@@ -862,7 +862,7 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
*/
public int getFloatingSearchBarRestingMarginEnd() {
DeviceProfile dp = mActivityContext.getDeviceProfile();
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(mContext);
}
private void layoutBelowSearchContainer(View v, boolean includeTabsMargin) {
@@ -1165,7 +1165,7 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
if (grid.isVerticalBarLayout()) {
setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
} else {
int topPadding = grid.allAppsTopPadding;
int topPadding = grid.allAppsPadding.top;
if (isSearchBarFloating() && !grid.isTablet) {
topPadding += getResources().getDimensionPixelSize(
R.dimen.all_apps_additional_top_padding_floating_search);
@@ -1227,7 +1227,7 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
int bottomPadding = Math.max(mInsets.bottom, mNavBarScrimHeight);
mAH.forEach(adapterHolder -> {
adapterHolder.mPadding.bottom = bottomPadding;
adapterHolder.mPadding.left = adapterHolder.mPadding.right = grid.allAppsLeftRightPadding;
adapterHolder.mPadding.left = adapterHolder.mPadding.right = grid.allAppsPadding.left + grid.allAppsPadding.right;
adapterHolder.applyPadding();
});
}

View File

@@ -460,8 +460,8 @@ public class FloatingHeaderView extends LinearLayout implements
@Override
public void setInsets(Rect insets) {
int leftRightPadding = ActivityContext.lookupContext(getContext())
.getDeviceProfile().allAppsLeftRightPadding;
var dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
int leftRightPadding = dp.allAppsPadding.left + dp.allAppsPadding.right;
setPadding(leftRightPadding, getPaddingTop(), leftRightPadding, getPaddingBottom());
}

View File

@@ -131,7 +131,8 @@ public class WorkModeSwitch extends LinearLayout implements Insettable,
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
View parent = (View) getParent();
int allAppsLeftRightPadding = mActivityContext.getDeviceProfile().allAppsLeftRightPadding;
var dp = mActivityContext.getDeviceProfile();
int allAppsLeftRightPadding = dp.allAppsPadding.left + dp.allAppsPadding.right;
int size = parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight()
- 2 * allAppsLeftRightPadding;
int tabWidth = getTabWidth(getContext(), size);

View File

@@ -429,7 +429,7 @@ public final class FeatureFlags {
// TODO(Block 32): Empty block
public static final BooleanFlag ENABLE_RESPONSIVE_WORKSPACE = getDebugFlag(241386436,
"ENABLE_RESPONSIVE_WORKSPACE", DISABLED,
"ENABLE_RESPONSIVE_WORKSPACE", ENABLED,
"Enables new workspace grid calculations method.");
// TODO(Block 33): Clean up flags
public static final BooleanFlag ENABLE_ALL_APPS_RV_PREINFLATION = getDebugFlag(288161355,

View File

@@ -1,104 +0,0 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.responsive
import android.content.res.TypedArray
import com.android.launcher3.R
import com.android.launcher3.responsive.ResponsiveSpec.SpecType
import com.android.launcher3.util.ResourceHelper
class AllAppsSpecs(widthSpecs: List<AllAppsSpec>, heightSpecs: List<AllAppsSpec>) :
ResponsiveSpecs<AllAppsSpec>(widthSpecs, heightSpecs) {
fun getCalculatedWidthSpec(
columns: Int,
availableWidth: Int,
calculatedWorkspaceSpec: CalculatedWorkspaceSpec
): CalculatedAllAppsSpec {
check(calculatedWorkspaceSpec.spec.specType == SpecType.WIDTH) {
"Invalid specType for CalculatedWorkspaceSpec. " +
"Expected: ${SpecType.WIDTH} - " +
"Found: ${calculatedWorkspaceSpec.spec.specType}}"
}
val spec = getWidthSpec(availableWidth)
return CalculatedAllAppsSpec(availableWidth, columns, spec, calculatedWorkspaceSpec)
}
fun getCalculatedHeightSpec(
rows: Int,
availableHeight: Int,
calculatedWorkspaceSpec: CalculatedWorkspaceSpec
): CalculatedAllAppsSpec {
check(calculatedWorkspaceSpec.spec.specType == SpecType.HEIGHT) {
"Invalid specType for CalculatedWorkspaceSpec. " +
"Expected: ${SpecType.HEIGHT} - " +
"Found: ${calculatedWorkspaceSpec.spec.specType}}"
}
val spec = getHeightSpec(availableHeight)
return CalculatedAllAppsSpec(availableHeight, rows, spec, calculatedWorkspaceSpec)
}
companion object {
private const val XML_ALL_APPS_SPEC = "allAppsSpec"
@JvmStatic
fun create(resourceHelper: ResourceHelper): AllAppsSpecs {
val parser = ResponsiveSpecsParser(resourceHelper)
val specs = parser.parseXML(XML_ALL_APPS_SPEC, ::AllAppsSpec)
val (widthSpecs, heightSpecs) = specs.partition { it.specType == SpecType.WIDTH }
return AllAppsSpecs(widthSpecs, heightSpecs)
}
}
}
data class AllAppsSpec(
override val maxAvailableSize: Int,
override val specType: SpecType,
override val startPadding: SizeSpec,
override val endPadding: SizeSpec,
override val gutter: SizeSpec,
override val cellSize: SizeSpec
) : ResponsiveSpec(maxAvailableSize, specType, startPadding, endPadding, gutter, cellSize) {
init {
check(isValid()) { "Invalid AllAppsSpec found." }
}
constructor(
attrs: TypedArray,
specs: Map<String, SizeSpec>
) : this(
maxAvailableSize =
attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
specType =
SpecType.values()[
attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
)
}
class CalculatedAllAppsSpec(
availableSpace: Int,
cells: Int,
spec: AllAppsSpec,
calculatedWorkspaceSpec: CalculatedWorkspaceSpec
) : CalculatedResponsiveSpec(availableSpace, cells, spec, calculatedWorkspaceSpec)

View File

@@ -1,105 +0,0 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.responsive
import android.content.res.TypedArray
import com.android.launcher3.R
import com.android.launcher3.responsive.ResponsiveSpec.SpecType
import com.android.launcher3.util.ResourceHelper
class FolderSpecs(widthSpecs: List<FolderSpec>, heightSpecs: List<FolderSpec>) :
ResponsiveSpecs<FolderSpec>(widthSpecs, heightSpecs) {
fun getCalculatedWidthSpec(
columns: Int,
availableWidth: Int,
calculatedWorkspaceSpec: CalculatedWorkspaceSpec
): CalculatedFolderSpec {
check(calculatedWorkspaceSpec.spec.specType == SpecType.WIDTH) {
"Invalid specType for CalculatedWorkspaceSpec. " +
"Expected: ${SpecType.WIDTH} - " +
"Found: ${calculatedWorkspaceSpec.spec.specType}}"
}
val spec = getWidthSpec(availableWidth)
return CalculatedFolderSpec(availableWidth, columns, spec, calculatedWorkspaceSpec)
}
fun getCalculatedHeightSpec(
rows: Int,
availableHeight: Int,
calculatedWorkspaceSpec: CalculatedWorkspaceSpec
): CalculatedFolderSpec {
check(calculatedWorkspaceSpec.spec.specType == SpecType.HEIGHT) {
"Invalid specType for CalculatedWorkspaceSpec. " +
"Expected: ${SpecType.HEIGHT} - " +
"Found: ${calculatedWorkspaceSpec.spec.specType}}"
}
val spec = getHeightSpec(availableHeight)
return CalculatedFolderSpec(availableHeight, rows, spec, calculatedWorkspaceSpec)
}
companion object {
private const val XML_FOLDER_SPEC = "folderSpec"
@JvmStatic
fun create(resourceHelper: ResourceHelper): FolderSpecs {
val parser = ResponsiveSpecsParser(resourceHelper)
val specs = parser.parseXML(XML_FOLDER_SPEC, ::FolderSpec)
val (widthSpecs, heightSpecs) = specs.partition { it.specType == SpecType.WIDTH }
return FolderSpecs(widthSpecs, heightSpecs)
}
}
}
data class FolderSpec(
override val maxAvailableSize: Int,
override val specType: SpecType,
override val startPadding: SizeSpec,
override val endPadding: SizeSpec,
override val gutter: SizeSpec,
override val cellSize: SizeSpec
) : ResponsiveSpec(maxAvailableSize, specType, startPadding, endPadding, gutter, cellSize) {
init {
check(isValid()) { "Invalid FolderSpec found." }
}
constructor(
attrs: TypedArray,
specs: Map<String, SizeSpec>
) : this(
maxAvailableSize =
attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
specType =
SpecType.values()[
attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
)
}
class CalculatedFolderSpec(
availableSpace: Int,
cells: Int,
spec: FolderSpec,
calculatedWorkspaceSpec: CalculatedWorkspaceSpec
) : CalculatedResponsiveSpec(availableSpace, cells, spec, calculatedWorkspaceSpec)

View File

@@ -1,122 +0,0 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.responsive
import android.content.res.TypedArray
import android.util.Log
import com.android.launcher3.R
import com.android.launcher3.util.ResourceHelper
class HotseatSpecs(val specs: List<HotseatSpec>) {
fun getCalculatedHeightSpec(availableHeight: Int): CalculatedHotseatSpec {
val spec = specs.firstOrNull { availableHeight <= it.maxAvailableSize }
check(spec != null) { "No available height spec found within $availableHeight." }
return CalculatedHotseatSpec(availableHeight, spec)
}
companion object {
private const val XML_HOTSEAT_SPEC = "hotseatSpec"
@JvmStatic
fun create(resourceHelper: ResourceHelper): HotseatSpecs {
val parser = ResponsiveSpecsParser(resourceHelper)
val specs = parser.parseXML(XML_HOTSEAT_SPEC, ::HotseatSpec)
return HotseatSpecs(specs.filter { it.specType == ResponsiveSpec.SpecType.HEIGHT })
}
}
}
data class HotseatSpec(
val maxAvailableSize: Int,
val specType: ResponsiveSpec.SpecType,
val hotseatQsbSpace: SizeSpec
) {
init {
check(isValid()) { "Invalid HotseatSpec found." }
}
constructor(
attrs: TypedArray,
specs: Map<String, SizeSpec>
) : this(
maxAvailableSize =
attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
specType =
ResponsiveSpec.SpecType.values()[
attrs.getInt(
R.styleable.ResponsiveSpec_specType,
ResponsiveSpec.SpecType.HEIGHT.ordinal
)],
hotseatQsbSpace = specs.getOrError(SizeSpec.XmlTags.HOTSEAT_QSB_SPACE)
)
fun isValid(): Boolean {
if (maxAvailableSize <= 0) {
Log.e(LOG_TAG, "${this::class.simpleName}#isValid - maxAvailableSize <= 0")
return false
}
// All specs need to be individually valid
if (!allSpecsAreValid()) {
Log.e(LOG_TAG, "${this::class.simpleName}#isValid - !allSpecsAreValid()")
return false
}
return true
}
private fun allSpecsAreValid(): Boolean {
return hotseatQsbSpace.isValid() && hotseatQsbSpace.onlyFixedSize()
}
companion object {
private const val LOG_TAG = "HotseatSpec"
}
}
class CalculatedHotseatSpec(val availableSpace: Int, val spec: HotseatSpec) {
var hotseatQsbSpace: Int = 0
private set
init {
hotseatQsbSpace = spec.hotseatQsbSpace.getCalculatedValue(availableSpace)
}
override fun hashCode(): Int {
var result = availableSpace.hashCode()
result = 31 * result + hotseatQsbSpace.hashCode()
result = 31 * result + spec.hashCode()
return result
}
override fun equals(other: Any?): Boolean {
return other is CalculatedHotseatSpec &&
availableSpace == other.availableSpace &&
hotseatQsbSpace == other.hotseatQsbSpace &&
spec == other.spec
}
override fun toString(): String {
return "${this::class.simpleName}(" +
"availableSpace=$availableSpace, hotseatQsbSpace=$hotseatQsbSpace, " +
"${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
")"
}
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.responsive
import android.content.res.TypedArray
import android.util.Log
import com.android.launcher3.R
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
import com.android.launcher3.util.ResourceHelper
class HotseatSpecsProvider(groupOfSpecs: List<ResponsiveSpecGroup<HotseatSpec>>) {
private val groupOfSpecs: List<ResponsiveSpecGroup<HotseatSpec>>
init {
this.groupOfSpecs = groupOfSpecs.sortedBy { it.aspectRatio }
}
fun getSpecsByAspectRatio(aspectRatio: Float): ResponsiveSpecGroup<HotseatSpec> {
check(aspectRatio > 0f) { "Invalid aspect ratio! The value should be bigger than 0." }
val specsGroup = groupOfSpecs.firstOrNull { aspectRatio <= it.aspectRatio }
check(specsGroup != null) { "No available spec with aspectRatio within $aspectRatio." }
return specsGroup
}
private fun getSpecIgnoringDimensionType(
availableSize: Int,
specsGroup: ResponsiveSpecGroup<HotseatSpec>
): HotseatSpec? {
val specWidth = specsGroup.widthSpecs.firstOrNull { availableSize <= it.maxAvailableSize }
val specHeight = specsGroup.heightSpecs.firstOrNull { availableSize <= it.maxAvailableSize }
return specWidth ?: specHeight
}
fun getCalculatedSpec(
aspectRatio: Float,
dimensionType: DimensionType,
availableSpace: Int,
): CalculatedHotseatSpec {
val specsGroup = getSpecsByAspectRatio(aspectRatio)
// TODO(b/315548992): Ignore the dimension type to prevent crash before launcher
// data migration is finished. The restore process allows the initialization of
// an invalid or disabled grid until the data is restored and migrated.
val spec = getSpecIgnoringDimensionType(availableSpace, specsGroup)
check(spec != null) { "No available spec found within $availableSpace. $specsGroup" }
// val spec = specsGroup.getSpec(dimensionType, availableSpace)
return CalculatedHotseatSpec(availableSpace, spec)
}
companion object {
@JvmStatic
fun create(resourceHelper: ResourceHelper): HotseatSpecsProvider {
val parser = ResponsiveSpecsParser(resourceHelper)
val specs = parser.parseXML(ResponsiveSpecType.Hotseat, ::HotseatSpec)
return HotseatSpecsProvider(specs)
}
}
}
data class HotseatSpec(
override val maxAvailableSize: Int,
override val dimensionType: DimensionType,
override val specType: ResponsiveSpecType,
val hotseatQsbSpace: SizeSpec,
val edgePadding: SizeSpec
) : IResponsiveSpec {
init {
check(isValid()) { "Invalid HotseatSpec found." }
}
constructor(
responsiveSpecType: ResponsiveSpecType,
attrs: TypedArray,
specs: Map<String, SizeSpec>
) : this(
maxAvailableSize =
attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
dimensionType =
DimensionType.entries[
attrs.getInt(
R.styleable.ResponsiveSpec_dimensionType,
DimensionType.HEIGHT.ordinal
)],
specType = responsiveSpecType,
hotseatQsbSpace = specs.getOrError(SizeSpec.XmlTags.HOTSEAT_QSB_SPACE),
edgePadding = specs.getOrError(SizeSpec.XmlTags.EDGE_PADDING)
)
fun isValid(): Boolean {
if (maxAvailableSize <= 0) {
logError("The property maxAvailableSize must be higher than 0.")
return false
}
// All specs need to be individually valid
if (!allSpecsAreValid()) {
logError("One or more specs are invalid!")
return false
}
if (!isValidFixedSize()) {
logError("The total Fixed Size used must be lower or equal to $maxAvailableSize.")
return false
}
return true
}
private fun allSpecsAreValid(): Boolean {
return hotseatQsbSpace.isValid() &&
hotseatQsbSpace.onlyFixedSize() &&
edgePadding.isValid() &&
edgePadding.onlyFixedSize()
}
private fun isValidFixedSize() =
hotseatQsbSpace.fixedSize + edgePadding.fixedSize <= maxAvailableSize
private fun logError(message: String) {
Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
}
companion object {
private const val LOG_TAG = "HotseatSpec"
}
}
class CalculatedHotseatSpec(val availableSpace: Int, val spec: HotseatSpec) {
var hotseatQsbSpace: Int = 0
private set
var edgePadding: Int = 0
private set
init {
hotseatQsbSpace = spec.hotseatQsbSpace.getCalculatedValue(availableSpace)
edgePadding = spec.edgePadding.getCalculatedValue(availableSpace)
}
override fun hashCode(): Int {
var result = availableSpace.hashCode()
result = 31 * result + hotseatQsbSpace.hashCode()
result = 31 * result + edgePadding.hashCode()
result = 31 * result + spec.hashCode()
return result
}
override fun equals(other: Any?): Boolean {
return other is CalculatedHotseatSpec &&
availableSpace == other.availableSpace &&
hotseatQsbSpace == other.hotseatQsbSpace &&
edgePadding == other.edgePadding &&
spec == other.spec
}
override fun toString(): String {
return "${this::class.simpleName}(" +
"availableSpace=$availableSpace, hotseatQsbSpace=$hotseatQsbSpace, " +
"edgePadding=$edgePadding, " +
"${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
")"
}
}

View File

@@ -0,0 +1,200 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.responsive
import android.content.res.TypedArray
import android.util.Log
import com.android.launcher3.R
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
import com.android.launcher3.util.ResourceHelper
class ResponsiveCellSpecsProvider(groupOfSpecs: List<ResponsiveSpecGroup<CellSpec>>) {
private val groupOfSpecs: List<ResponsiveSpecGroup<CellSpec>>
init {
this.groupOfSpecs =
groupOfSpecs
.onEach { group ->
check(group.widthSpecs.isEmpty() && group.heightSpecs.isNotEmpty()) {
"${this::class.simpleName} is invalid, only heightSpecs are allowed - " +
"width list size = ${group.widthSpecs.size}; " +
"height list size = ${group.heightSpecs.size}."
}
}
.sortedBy { it.aspectRatio }
}
fun getSpecsByAspectRatio(aspectRatio: Float): ResponsiveSpecGroup<CellSpec> {
check(aspectRatio > 0f) { "Invalid aspect ratio! The value should be bigger than 0." }
val specsGroup = groupOfSpecs.firstOrNull { aspectRatio <= it.aspectRatio }
check(specsGroup != null) { "No available spec with aspectRatio within $aspectRatio." }
return specsGroup
}
fun getCalculatedSpec(aspectRatio: Float, availableHeightSpace: Int): CalculatedCellSpec {
val specsGroup = getSpecsByAspectRatio(aspectRatio)
val spec = specsGroup.getSpec(DimensionType.HEIGHT, availableHeightSpace)
return CalculatedCellSpec(availableHeightSpace, spec)
}
fun getCalculatedSpec(
aspectRatio: Float,
availableHeightSpace: Int,
workspaceCellSpec: CalculatedCellSpec
): CalculatedCellSpec {
val specsGroup = getSpecsByAspectRatio(aspectRatio)
val spec = specsGroup.getSpec(DimensionType.HEIGHT, availableHeightSpace)
return CalculatedCellSpec(availableHeightSpace, spec, workspaceCellSpec)
}
companion object {
@JvmStatic
fun create(resourceHelper: ResourceHelper): ResponsiveCellSpecsProvider {
val parser = ResponsiveSpecsParser(resourceHelper)
val specs = parser.parseXML(ResponsiveSpecType.Cell, ::CellSpec)
return ResponsiveCellSpecsProvider(specs)
}
}
}
data class CellSpec(
override val maxAvailableSize: Int,
override val dimensionType: DimensionType,
override val specType: ResponsiveSpecType,
val iconSize: SizeSpec,
val iconTextSize: SizeSpec,
val iconDrawablePadding: SizeSpec
) : IResponsiveSpec {
init {
check(isValid()) { "Invalid CellSpec found." }
}
constructor(
responsiveSpecType: ResponsiveSpecType,
attrs: TypedArray,
specs: Map<String, SizeSpec>
) : this(
maxAvailableSize =
attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
dimensionType =
DimensionType.entries[
attrs.getInt(
R.styleable.ResponsiveSpec_dimensionType,
DimensionType.HEIGHT.ordinal
)],
specType = responsiveSpecType,
iconSize = specs.getOrError(SizeSpec.XmlTags.ICON_SIZE),
iconTextSize = specs.getOrError(SizeSpec.XmlTags.ICON_TEXT_SIZE),
iconDrawablePadding = specs.getOrError(SizeSpec.XmlTags.ICON_DRAWABLE_PADDING)
)
fun isValid(): Boolean {
if (maxAvailableSize <= 0) {
logError("The property maxAvailableSize must be higher than 0.")
return false
}
// All specs need to be individually valid
if (!allSpecsAreValid()) {
logError("Specs must be either Fixed Size or Match Workspace!")
return false
}
if (!isValidFixedSize()) {
logError("The total Fixed Size used must be lower or equal to $maxAvailableSize.")
return false
}
return true
}
private fun isValidFixedSize(): Boolean {
val totalSize = iconSize.fixedSize + iconTextSize.fixedSize + iconDrawablePadding.fixedSize
return totalSize <= maxAvailableSize
}
private fun allSpecsAreValid(): Boolean {
return (iconSize.fixedSize > 0f || iconSize.matchWorkspace) &&
(iconTextSize.fixedSize >= 0f || iconTextSize.matchWorkspace) &&
(iconDrawablePadding.fixedSize >= 0f || iconDrawablePadding.matchWorkspace)
}
private fun logError(message: String) {
Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
}
companion object {
private const val LOG_TAG = "CellSpec"
}
}
data class CalculatedCellSpec(
val availableSpace: Int,
val spec: CellSpec,
val iconSize: Int,
val iconTextSize: Int,
val iconDrawablePadding: Int
) {
constructor(
availableSpace: Int,
spec: CellSpec
) : this(
availableSpace = availableSpace,
spec = spec,
iconSize = spec.iconSize.getCalculatedValue(availableSpace),
iconTextSize = spec.iconTextSize.getCalculatedValue(availableSpace),
iconDrawablePadding = spec.iconDrawablePadding.getCalculatedValue(availableSpace)
)
constructor(
availableSpace: Int,
spec: CellSpec,
workspaceCellSpec: CalculatedCellSpec
) : this(
availableSpace = availableSpace,
spec = spec,
iconSize = getCalculatedValue(availableSpace, spec.iconSize, workspaceCellSpec.iconSize),
iconTextSize =
getCalculatedValue(availableSpace, spec.iconTextSize, workspaceCellSpec.iconTextSize),
iconDrawablePadding =
getCalculatedValue(
availableSpace,
spec.iconDrawablePadding,
workspaceCellSpec.iconDrawablePadding
)
)
companion object {
private fun getCalculatedValue(
availableSpace: Int,
spec: SizeSpec,
workspaceValue: Int
): Int =
if (spec.matchWorkspace) workspaceValue else spec.getCalculatedValue(availableSpace)
}
override fun toString(): String {
return "${this::class.simpleName}(" +
"availableSpace=$availableSpace, iconSize=$iconSize, " +
"iconTextSize=$iconTextSize, iconDrawablePadding=$iconDrawablePadding, " +
"${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
")"
}
}

View File

@@ -16,81 +16,107 @@
package com.android.launcher3.responsive
import android.content.res.TypedArray
import android.util.Log
import com.android.launcher3.R
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
/**
* Base class for responsive specs that holds a list of width and height specs.
* Interface for responsive grid specs
*
* @param widthSpecs List of width responsive specifications
* @param heightSpecs List of height responsive specifications
* @property maxAvailableSize indicates the breakpoint to use this specification.
* @property dimensionType indicates whether the paddings and gutters will be applied vertically or
* horizontally.
* @property specType a [ResponsiveSpecType] that indicates the type of the spec.
*/
abstract class ResponsiveSpecs<T : ResponsiveSpec>(
val widthSpecs: List<T>,
val heightSpecs: List<T>
) {
init {
check(widthSpecs.isNotEmpty() && heightSpecs.isNotEmpty()) {
"${this::class.simpleName} is incomplete - " +
"width list size = ${widthSpecs.size}; " +
"height list size = ${heightSpecs.size}."
}
}
/**
* Get a [ResponsiveSpec] for width within the breakpoint.
*
* @param availableWidth The width breakpoint for the spec
* @return A [ResponsiveSpec] for width.
*/
fun getWidthSpec(availableWidth: Int): T {
val spec = widthSpecs.firstOrNull { availableWidth <= it.maxAvailableSize }
check(spec != null) { "No available width spec found within $availableWidth." }
return spec
}
/**
* Get a [ResponsiveSpec] for height within the breakpoint.
*
* @param availableHeight The height breakpoint for the spec
* @return A [ResponsiveSpec] for height.
*/
fun getHeightSpec(availableHeight: Int): T {
val spec = heightSpecs.firstOrNull { availableHeight <= it.maxAvailableSize }
check(spec != null) { "No available height spec found within $availableHeight." }
return spec
}
interface IResponsiveSpec {
val maxAvailableSize: Int
val dimensionType: ResponsiveSpec.DimensionType
val specType: ResponsiveSpecType
}
/**
* Base class for a responsive specification that is used to calculate the paddings, gutter and cell
* Class for a responsive specification that is used to calculate the paddings, gutter and cell
* size.
*
* @param maxAvailableSize indicates the breakpoint to use this specification.
* @param specType indicates whether the paddings and gutters will be applied vertically or
* @param dimensionType indicates whether the paddings and gutters will be applied vertically or
* horizontally.
* @param specType a [ResponsiveSpecType] that indicates the type of the spec.
* @param startPadding padding used at the top or left (right in RTL) in the workspace folder.
* @param endPadding padding used at the bottom or right (left in RTL) in the workspace folder.
* @param gutter the space between the cells vertically or horizontally depending on the [specType].
* @param cellSize height or width of the cell depending on the [specType].
* @param gutter the space between the cells vertically or horizontally depending on the
* [dimensionType].
* @param cellSize height or width of the cell depending on the [dimensionType].
*/
abstract class ResponsiveSpec(
open val maxAvailableSize: Int,
open val specType: SpecType,
open val startPadding: SizeSpec,
open val endPadding: SizeSpec,
open val gutter: SizeSpec,
open val cellSize: SizeSpec
) {
open fun isValid(): Boolean {
data class ResponsiveSpec(
override val maxAvailableSize: Int,
override val dimensionType: DimensionType,
override val specType: ResponsiveSpecType,
val startPadding: SizeSpec,
val endPadding: SizeSpec,
val gutter: SizeSpec,
val cellSize: SizeSpec,
) : IResponsiveSpec {
init {
check(isValid()) { "Invalid ResponsiveSpec found." }
}
constructor(
responsiveSpecType: ResponsiveSpecType,
attrs: TypedArray,
specs: Map<String, SizeSpec>
) : this(
maxAvailableSize =
attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
dimensionType =
DimensionType.entries[
attrs.getInt(
R.styleable.ResponsiveSpec_dimensionType,
DimensionType.HEIGHT.ordinal
)],
specType = responsiveSpecType,
startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
)
fun isValid(): Boolean {
if (
(specType == ResponsiveSpecType.Workspace) &&
(startPadding.matchWorkspace ||
endPadding.matchWorkspace ||
gutter.matchWorkspace ||
cellSize.matchWorkspace)
) {
logError("Workspace spec provided must not have any match workspace value.")
return false
}
if (maxAvailableSize <= 0) {
Log.e(LOG_TAG, "${this::class.simpleName}#isValid - maxAvailableSize <= 0")
logError("The property maxAvailableSize must be higher than 0.")
return false
}
// All specs need to be individually valid
if (!allSpecsAreValid()) {
Log.e(LOG_TAG, "${this::class.simpleName}#isValid - !allSpecsAreValid()")
logError("One or more specs are invalid!")
return false
}
if (!isValidRemainderSpace()) {
logError("The total Remainder Space used must be lower or equal to 100%.")
return false
}
if (!isValidAvailableSpace()) {
logError("The total Available Space used must be lower or equal to 100%.")
return false
}
if (!isValidFixedSize()) {
logError("The total Fixed Size used must be lower or equal to $maxAvailableSize.")
return false
}
@@ -104,13 +130,47 @@ abstract class ResponsiveSpec(
cellSize.isValid()
}
enum class SpecType {
private fun isValidRemainderSpace(): Boolean {
// TODO(b/313621277): This validation must be update do accept only 0 or 1 instead of <= 1f.
return startPadding.ofRemainderSpace +
endPadding.ofRemainderSpace +
gutter.ofRemainderSpace +
cellSize.ofRemainderSpace <= 1f
}
private fun isValidAvailableSpace(): Boolean {
return startPadding.ofAvailableSpace +
endPadding.ofAvailableSpace +
gutter.ofAvailableSpace +
cellSize.ofAvailableSpace < 1f
}
private fun isValidFixedSize(): Boolean {
return startPadding.fixedSize +
endPadding.fixedSize +
gutter.fixedSize +
cellSize.fixedSize <= maxAvailableSize
}
private fun logError(message: String) {
Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
}
enum class DimensionType {
HEIGHT,
WIDTH
}
companion object {
private const val LOG_TAG = "ResponsiveSpec"
enum class ResponsiveSpecType(val xmlTag: String) {
AllApps("allAppsSpec"),
Folder("folderSpec"),
Workspace("workspaceSpec"),
Hotseat("hotseatSpec"),
Cell("cellSpec")
}
}
}
@@ -118,7 +178,10 @@ abstract class ResponsiveSpec(
* Calculated responsive specs contains the final paddings, gutter and cell size in pixels after
* they are calculated from the available space, cells and workspace specs.
*/
sealed class CalculatedResponsiveSpec {
class CalculatedResponsiveSpec {
var aspectRatio: Float = Float.NaN
private set
var availableSpace: Int = 0
private set
@@ -141,11 +204,13 @@ sealed class CalculatedResponsiveSpec {
private set
constructor(
aspectRatio: Float,
availableSpace: Int,
cells: Int,
spec: ResponsiveSpec,
calculatedWorkspaceSpec: CalculatedWorkspaceSpec
calculatedWorkspaceSpec: CalculatedResponsiveSpec
) {
this.aspectRatio = aspectRatio
this.availableSpace = availableSpace
this.cells = cells
this.spec = spec
@@ -165,7 +230,8 @@ sealed class CalculatedResponsiveSpec {
updateRemainderSpaces(availableSpace, cells, spec)
}
constructor(availableSpace: Int, cells: Int, spec: ResponsiveSpec) {
constructor(aspectRatio: Float, availableSpace: Int, cells: Int, spec: ResponsiveSpec) {
this.aspectRatio = aspectRatio
this.availableSpace = availableSpace
this.cells = cells
this.spec = spec
@@ -179,6 +245,8 @@ sealed class CalculatedResponsiveSpec {
updateRemainderSpaces(availableSpace, cells, spec)
}
fun isResponsiveSpecType(type: ResponsiveSpecType) = spec.specType == type
private fun updateRemainderSpaces(availableSpace: Int, cells: Int, spec: ResponsiveSpec) {
val gutters = cells - 1
val usedSpace = startPaddingPx + endPaddingPx + (gutterPx * gutters) + (cellSizePx * cells)
@@ -213,10 +281,11 @@ sealed class CalculatedResponsiveSpec {
}
override fun toString(): String {
return "${this::class.simpleName}(" +
return "Calculated${spec.specType}Spec(" +
"availableSpace=$availableSpace, cells=$cells, startPaddingPx=$startPaddingPx, " +
"endPaddingPx=$endPaddingPx, gutterPx=$gutterPx, cellSizePx=$cellSizePx, " +
"${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
"aspectRatio=${aspectRatio}, " +
"${spec.specType}Spec.maxAvailableSize=${spec.maxAvailableSize}" +
")"
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.responsive
import android.content.res.TypedArray
import com.android.launcher3.R
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
/**
* Base class for responsive specs that holds a list of width and height specs.
*
* @param widthSpecs List of width responsive specifications
* @param heightSpecs List of height responsive specifications
*/
class ResponsiveSpecGroup<T : IResponsiveSpec>(
val aspectRatio: Float,
widthSpecs: List<T>,
heightSpecs: List<T>
) {
val widthSpecs: List<T>
val heightSpecs: List<T>
init {
check(aspectRatio > 0f) { "Invalid aspect ratio! Aspect ratio should be bigger than zero." }
this.widthSpecs = widthSpecs.sortedBy { it.maxAvailableSize }
this.heightSpecs = heightSpecs.sortedBy { it.maxAvailableSize }
}
/**
* Get a [ResponsiveSpec] within the breakpoint.
*
* @param type Type of the spec to be retrieved (width or height)
* @param availableSize The breakpoint for the spec
* @return A [ResponsiveSpec].
*/
fun getSpec(type: DimensionType, availableSize: Int): T {
val spec =
if (type == DimensionType.WIDTH) {
widthSpecs.firstOrNull { availableSize <= it.maxAvailableSize }
} else {
heightSpecs.firstOrNull { availableSize <= it.maxAvailableSize }
}
check(spec != null) { "No available $type spec found within $availableSize. $this" }
return spec
}
override fun toString(): String {
fun printSpec(spec: IResponsiveSpec) =
when (spec.specType) {
ResponsiveSpecType.AllApps,
ResponsiveSpecType.Folder,
ResponsiveSpecType.Workspace -> (spec as ResponsiveSpec).toString()
ResponsiveSpecType.Hotseat -> (spec as HotseatSpec).toString()
ResponsiveSpecType.Cell -> (spec as CellSpec).toString()
}
val widthSpecsString = widthSpecs.joinToString(", ") { printSpec(it) }
val heightSpecsString = heightSpecs.joinToString(", ") { printSpec(it) }
return "ResponsiveSpecGroup(" +
"aspectRatio=${aspectRatio}, " +
"widthSpecs=[${widthSpecsString}], " +
"heightSpecs=[${heightSpecsString}]" +
")"
}
companion object {
const val XML_GROUP_NAME = "specs"
fun <T : IResponsiveSpec> create(
attrs: TypedArray,
specs: List<T>
): ResponsiveSpecGroup<T> {
val (widthSpecs, heightSpecs) =
specs.partition { it.dimensionType == DimensionType.WIDTH }
val aspectRatio = attrs.getFloat(R.styleable.ResponsiveSpecGroup_maxAspectRatio, 0f)
return ResponsiveSpecGroup(aspectRatio, widthSpecs, heightSpecs)
}
}
}

View File

@@ -20,13 +20,88 @@ import android.content.res.TypedArray
import android.content.res.XmlResourceParser
import android.util.Xml
import com.android.launcher3.R
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
import com.android.launcher3.util.ResourceHelper
import java.io.IOException
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import java.io.IOException
class ResponsiveSpecsParser(private val resourceHelper: ResourceHelper) {
fun <T : IResponsiveSpec> parseXML(
responsiveSpecType: ResponsiveSpecType,
map:
(
responsiveSpecType: ResponsiveSpecType,
attributes: TypedArray,
sizeSpecs: Map<String, SizeSpec>
) -> T
): List<ResponsiveSpecGroup<T>> {
val parser: XmlResourceParser = resourceHelper.getXml()
try {
val groups = mutableListOf<ResponsiveSpecGroup<T>>()
val specs = mutableListOf<T>()
var groupAttrs: TypedArray? = null
var eventType = parser.eventType
while (eventType != XmlPullParser.END_DOCUMENT) {
// Parsing Group
when {
parser starts ResponsiveSpecGroup.XML_GROUP_NAME -> {
groupAttrs =
resourceHelper.obtainStyledAttributes(
Xml.asAttributeSet(parser),
R.styleable.ResponsiveSpecGroup
)
}
parser ends ResponsiveSpecGroup.XML_GROUP_NAME -> {
checkNotNull(groupAttrs)
groups += ResponsiveSpecGroup.create(groupAttrs, specs)
specs.clear()
groupAttrs.recycle()
groupAttrs = null
}
// Mapping Spec to WorkspaceSpec, AllAppsSpec, FolderSpecs, HotseatSpec
parser starts responsiveSpecType.xmlTag -> {
val attrs =
resourceHelper.obtainStyledAttributes(
Xml.asAttributeSet(parser),
R.styleable.ResponsiveSpec
)
val sizeSpecs = parseSizeSpecs(parser)
specs += map(responsiveSpecType, attrs, sizeSpecs)
attrs.recycle()
}
}
eventType = parser.next()
}
parser.close()
// All the specs should have been linked to a group, otherwise the XML is invalid
check(specs.isEmpty()) {
throw InvalidResponsiveGridSpec(
"Invalid XML. ${specs.size} specs not linked to a group."
)
}
return groups
} catch (e: Exception) {
when (e) {
is NoSuchFieldException,
is IOException,
is XmlPullParserException ->
throw RuntimeException("Failure parsing specs file.", e)
else -> throw e
}
} finally {
parser.close()
}
}
private fun parseSizeSpecs(parser: XmlResourceParser): Map<String, SizeSpec> {
val parentName = parser.name
parser.next()
@@ -42,49 +117,15 @@ class ResponsiveSpecsParser(private val resourceHelper: ResourceHelper) {
return result
}
fun <T> parseXML(
tagName: String,
map: (attributes: TypedArray, sizeSpecs: Map<String, SizeSpec>) -> T
): List<T> {
val parser: XmlResourceParser = resourceHelper.getXml()
private infix fun XmlResourceParser.starts(tag: String): Boolean =
name == tag && eventType == XmlPullParser.START_TAG
try {
val list = mutableListOf<T>()
var eventType = parser.eventType
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlResourceParser.START_TAG && parser.name == tagName) {
val attrs =
resourceHelper.obtainStyledAttributes(
Xml.asAttributeSet(parser),
R.styleable.ResponsiveSpec
)
val sizeSpecs = parseSizeSpecs(parser)
list += map(attrs, sizeSpecs)
attrs.recycle()
}
eventType = parser.next()
}
parser.close()
return list
} catch (e: Exception) {
when (e) {
is NoSuchFieldException,
is IOException,
is XmlPullParserException ->
throw RuntimeException("Failure parsing specs file.", e)
else -> throw e
}
} finally {
parser.close()
}
}
private infix fun XmlResourceParser.ends(tag: String): Boolean =
name == tag && eventType == XmlPullParser.END_TAG
}
fun Map<String, SizeSpec>.getOrError(key: String): SizeSpec {
return this.getOrElse(key) { error("Attr '$key' must be defined.") }
}
class InvalidResponsiveGridSpec(message: String) : Exception(message)

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.responsive
import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
import com.android.launcher3.util.ResourceHelper
/**
* A class to provide responsive grid specs for workspace, folder and all apps.
*
* This class is responsible for provide width and height [CalculatedResponsiveSpec] to be used for
* the correct placement of the workspace, all apps and folders.
*
* @param type A [ResponsiveSpecType] to indicates the type of the spec.
* @param groupOfSpecs Groups of responsive specifications
*/
class ResponsiveSpecsProvider(
val type: ResponsiveSpecType,
groupOfSpecs: List<ResponsiveSpecGroup<ResponsiveSpec>>
) {
private val groupOfSpecs: List<ResponsiveSpecGroup<ResponsiveSpec>>
init {
this.groupOfSpecs =
groupOfSpecs
.onEach { group ->
check(group.widthSpecs.isNotEmpty() && group.heightSpecs.isNotEmpty()) {
"${this::class.simpleName} is incomplete - " +
"width list size = ${group.widthSpecs.size}; " +
"height list size = ${group.heightSpecs.size}."
}
}
.sortedBy { it.aspectRatio }
}
fun getSpecsByAspectRatio(aspectRatio: Float): ResponsiveSpecGroup<ResponsiveSpec> {
check(aspectRatio > 0f) { "Invalid aspect ratio! The value should be bigger than 0." }
val specsGroup = groupOfSpecs.firstOrNull { aspectRatio <= it.aspectRatio }
checkNotNull(specsGroup) { "No available spec with aspectRatio within $aspectRatio." }
return specsGroup
}
/**
* Retrieves a responsive grid specification that matches the number of [numCells],
* * [availableSpace] and [aspectRatio].
*
* @param aspectRatio the device width divided by device height (aspect ratio) to filter the
* specifications
* @param dimensionType the grid axis of the spec width is x axis, height is y axis.
* @param numCells number of rows/columns in the grid
* @param availableSpace available width to filter the specifications
* @return A [CalculatedResponsiveSpec] that matches the parameters provided.
*/
fun getCalculatedSpec(
aspectRatio: Float,
dimensionType: DimensionType,
numCells: Int,
availableSpace: Int,
): CalculatedResponsiveSpec {
val specsGroup = getSpecsByAspectRatio(aspectRatio)
val spec = specsGroup.getSpec(dimensionType, availableSpace)
return CalculatedResponsiveSpec(aspectRatio, availableSpace, numCells, spec)
}
/**
* Retrieves a responsive grid specification that matches the number of [numCells],
* * [availableSpace] and [aspectRatio]. This function uses a [CalculatedResponsiveSpec] to
* match workspace when its true.
*
* @param aspectRatio the device width divided by device height (aspect ratio) to filter the
* specifications
* @param dimensionType the grid axis of the spec width is x axis, height is y axis.
* @param numCells number of rows/columns in the grid
* @param availableSpace available width to filter the specifications
* @param calculatedWorkspaceSpec the calculated workspace specification to use its values as
* base when matchWorkspace is true.
* @return A [CalculatedResponsiveSpec] that matches the parameters provided.
*/
fun getCalculatedSpec(
aspectRatio: Float,
dimensionType: DimensionType,
numCells: Int,
availableSpace: Int,
calculatedWorkspaceSpec: CalculatedResponsiveSpec
): CalculatedResponsiveSpec {
check(calculatedWorkspaceSpec.spec.dimensionType == dimensionType) {
"Invalid specType for CalculatedWorkspaceSpec. " +
"Expected: $dimensionType - " +
"Found: ${calculatedWorkspaceSpec.spec.dimensionType}}"
}
check(calculatedWorkspaceSpec.isResponsiveSpecType(ResponsiveSpecType.Workspace)) {
"Invalid specType for CalculatedWorkspaceSpec. " +
"Expected: ${ResponsiveSpecType.Workspace} - " +
"Found: ${calculatedWorkspaceSpec.spec.specType}}"
}
val specsGroup = getSpecsByAspectRatio(aspectRatio)
val spec = specsGroup.getSpec(dimensionType, availableSpace)
return CalculatedResponsiveSpec(
aspectRatio,
availableSpace,
numCells,
spec,
calculatedWorkspaceSpec
)
}
companion object {
@JvmStatic
fun create(
resourceHelper: ResourceHelper,
type: ResponsiveSpecType
): ResponsiveSpecsProvider {
val parser = ResponsiveSpecsParser(resourceHelper)
val specs = parser.parseXML(type, ::ResponsiveSpec)
return ResponsiveSpecsProvider(type, specs)
}
}
}

View File

@@ -121,6 +121,10 @@ data class SizeSpec(
const val GUTTER = "gutter"
const val CELL_SIZE = "cellSize"
const val HOTSEAT_QSB_SPACE = "hotseatQsbSpace"
const val EDGE_PADDING = "edgePadding"
const val ICON_SIZE = "iconSize"
const val ICON_TEXT_SIZE = "iconTextSize"
const val ICON_DRAWABLE_PADDING = "iconDrawablePadding"
}
companion object {

View File

@@ -1,98 +0,0 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.responsive
import android.content.res.TypedArray
import android.util.Log
import com.android.launcher3.R
import com.android.launcher3.responsive.ResponsiveSpec.SpecType
import com.android.launcher3.util.ResourceHelper
private const val TAG = "WorkspaceSpecs"
class WorkspaceSpecs(widthSpecs: List<WorkspaceSpec>, heightSpecs: List<WorkspaceSpec>) :
ResponsiveSpecs<WorkspaceSpec>(widthSpecs, heightSpecs) {
fun getCalculatedWidthSpec(columns: Int, availableWidth: Int): CalculatedWorkspaceSpec {
val spec = getWidthSpec(availableWidth)
return CalculatedWorkspaceSpec(availableWidth, columns, spec)
}
fun getCalculatedHeightSpec(rows: Int, availableHeight: Int): CalculatedWorkspaceSpec {
val spec = getHeightSpec(availableHeight)
return CalculatedWorkspaceSpec(availableHeight, rows, spec)
}
companion object {
private const val XML_WORKSPACE_SPEC = "workspaceSpec"
@JvmStatic
fun create(resourceHelper: ResourceHelper): WorkspaceSpecs {
val parser = ResponsiveSpecsParser(resourceHelper)
val specs = parser.parseXML(XML_WORKSPACE_SPEC, ::WorkspaceSpec)
val (widthSpecs, heightSpecs) = specs.partition { it.specType == SpecType.WIDTH }
return WorkspaceSpecs(widthSpecs, heightSpecs)
}
}
}
data class WorkspaceSpec(
override val maxAvailableSize: Int,
override val specType: SpecType,
override val startPadding: SizeSpec,
override val endPadding: SizeSpec,
override val gutter: SizeSpec,
override val cellSize: SizeSpec
) : ResponsiveSpec(maxAvailableSize, specType, startPadding, endPadding, gutter, cellSize) {
init {
check(isValid()) { "Invalid WorkspaceSpec found." }
}
constructor(
attrs: TypedArray,
specs: Map<String, SizeSpec>
) : this(
maxAvailableSize =
attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
specType =
SpecType.values()[
attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
)
override fun isValid(): Boolean {
// Workspace spec should not match workspace
if (
startPadding.matchWorkspace ||
endPadding.matchWorkspace ||
gutter.matchWorkspace ||
cellSize.matchWorkspace
) {
Log.e(TAG, "WorkspaceSpec#isValid - workspace shouldn't contain matchWorkspace!")
return false
}
return super.isValid()
}
}
class CalculatedWorkspaceSpec(availableSpace: Int, cells: Int, spec: WorkspaceSpec) :
CalculatedResponsiveSpec(availableSpace, cells, spec)

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.util
import com.android.launcher3.Utilities
import kotlin.math.max
class CellContentDimensions(
var iconSizePx: Int,
var iconDrawablePaddingPx: Int,
var iconTextSizePx: Int
) {
/**
* This method goes through some steps to reduce the padding between icon and label, icon size
* and then label size, until it can fit in the [cellHeightPx].
*
* @return the height of the content after being sized down.
*/
fun resizeToFitCellHeight(cellHeightPx: Int, iconSizeSteps: IconSizeSteps): Int {
var cellContentHeight = getCellContentHeight()
// Step 1. Decrease drawable padding
if (cellContentHeight > cellHeightPx) {
val diff = cellContentHeight - cellHeightPx
iconDrawablePaddingPx = max(0, iconDrawablePaddingPx - diff)
cellContentHeight = getCellContentHeight()
}
while (
(iconTextSizePx > iconSizeSteps.minimumIconLabelSize ||
iconSizePx > iconSizeSteps.minimumIconSize()) && cellContentHeight > cellHeightPx
) {
// Step 2. Decrease icon size
iconSizePx = iconSizeSteps.getNextLowerIconSize(iconSizePx)
cellContentHeight = getCellContentHeight()
// Step 3. Decrease label size
if (cellContentHeight > cellHeightPx) {
iconTextSizePx =
max(
iconSizeSteps.minimumIconLabelSize,
iconTextSizePx - IconSizeSteps.TEXT_STEP
)
cellContentHeight = getCellContentHeight()
}
}
return cellContentHeight
}
/** Calculate new cellContentHeight */
fun getCellContentHeight(): Int {
val iconTextHeight = Utilities.calculateTextHeight(iconTextSizePx.toFloat())
return iconSizePx + iconDrawablePaddingPx + iconTextHeight
}
}

View File

@@ -20,9 +20,8 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TRANSIENT_TASKBAR;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
@@ -32,6 +31,7 @@ import android.annotation.TargetApi;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
@@ -62,30 +62,31 @@ import java.util.Set;
import java.util.StringJoiner;
/**
* Utility class to cache properties of default display to avoid a system RPC on
* every call.
* Utility class to cache properties of default display to avoid a system RPC on every call.
*/
@SuppressLint("NewApi")
public class DisplayController implements ComponentCallbacks, SafeCloseable {
private static final String TAG = "DisplayController";
private static final boolean DEBUG = false;
private static boolean sTransientTaskbarStatusForTests;
private static boolean sTransientTaskbarStatusForTests = true;
// TODO(b/254119092) remove all logs with this tag
public static final String TASKBAR_NOT_DESTROYED_TAG = "b/254119092";
public static final MainThreadInitializedObject<DisplayController> INSTANCE = new MainThreadInitializedObject<>(
DisplayController::new);
public static final MainThreadInitializedObject<DisplayController> INSTANCE =
new MainThreadInitializedObject<>(DisplayController::new);
public static final int CHANGE_ACTIVE_SCREEN = 1 << 0;
public static final int CHANGE_ROTATION = 1 << 1;
public static final int CHANGE_DENSITY = 1 << 2;
public static final int CHANGE_SUPPORTED_BOUNDS = 1 << 3;
public static final int CHANGE_NAVIGATION_MODE = 1 << 4;
public static final int CHANGE_TASKBAR_PINNING = 1 << 5;
public static final int CHANGE_ALL = CHANGE_ACTIVE_SCREEN | CHANGE_ROTATION
| CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS | CHANGE_NAVIGATION_MODE;
| CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS | CHANGE_NAVIGATION_MODE
| CHANGE_TASKBAR_PINNING;
private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
private static final String TARGET_OVERLAY_PACKAGE = "android";
@@ -96,8 +97,7 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
// Null for SDK < S
private final Context mWindowContext;
// The callback in this listener updates DeviceProfile, which other listeners
// might depend on
// The callback in this listener updates DeviceProfile, which other listeners might depend on
private DisplayInfoChangeListener mPriorityListener;
private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
@@ -106,13 +106,17 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
private Info mInfo;
private boolean mDestroyed = false;
private final LauncherPrefs mPrefs;
private SharedPreferences.OnSharedPreferenceChangeListener
mTaskbarPinningPreferenceChangeListener;
@VisibleForTesting
protected DisplayController(Context context) {
mContext = context;
mDM = context.getSystemService(DisplayManager.class);
mPrefs = LauncherPrefs.get(context);
if (false) {
attachTaskbarPinningSharedPreferenceChangeListener(mContext);
}
Display display = mDM.getDisplay(DEFAULT_DISPLAY);
if (Utilities.ATLEAST_S) {
@@ -133,6 +137,21 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
FileLog.i(TAG, "(CTOR) perDisplayBounds: " + mInfo.mPerDisplayBounds);
}
private void attachTaskbarPinningSharedPreferenceChangeListener(Context context) {
mTaskbarPinningPreferenceChangeListener =
(sharedPreferences, key) -> {
if (TASKBAR_PINNING_KEY.equals(key)
&& mInfo.mIsTaskbarPinned != LauncherPrefs.get(mContext).get(
TASKBAR_PINNING)
) {
handleInfoChange(mWindowContext.getDisplay());
}
};
LauncherPrefs.get(context).addListener(
mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
}
/**
* Returns the current navigation mode
*/
@@ -144,27 +163,7 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
* Returns whether taskbar is transient.
*/
public static boolean isTransientTaskbar(Context context) {
return INSTANCE.get(context).isTransientTaskbar();
}
/**
* Returns whether taskbar is transient.
*/
public boolean isTransientTaskbar() {
// TODO(b/258604917): When running in test harness, use
// !sTransientTaskbarStatusForTests
// once tests are updated to expect new persistent behavior such as not allowing
// long press
// to stash.
if (!Utilities.isRunningInTestHarness()
&& ENABLE_TASKBAR_PINNING.get()
&& mPrefs.get(TASKBAR_PINNING)) {
return false;
}
return getInfo().navigationMode == NavigationMode.NO_BUTTON
&& (Utilities.isRunningInTestHarness()
? sTransientTaskbarStatusForTests
: ENABLE_TRANSIENT_TASKBAR.get());
return INSTANCE.get(context).getInfo().isTransientTaskbar();
}
/**
@@ -178,6 +177,10 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
@Override
public void close() {
mDestroyed = true;
if (false) {
LauncherPrefs.get(mContext).removeListener(
mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
}
if (mWindowContext != null) {
mWindowContext.unregisterComponentCallbacks(this);
} else {
@@ -192,10 +195,9 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
/**
* Invoked when display info has changed.
*
* @param context updated context associated with the display.
* @param info updated display information.
* @param flags bitmask indicating type of change.
* @param info updated display information.
* @param flags bitmask indicating type of change.
*/
void onDisplayInfoChanged(Context context, Info info, int flags);
}
@@ -232,14 +234,13 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
|| config.fontScale != mInfo.fontScale
|| display.getRotation() != mInfo.rotation
|| !mInfo.mScreenSizeDp.equals(
new PortraitSize(config.screenHeightDp, config.screenWidthDp))) {
new PortraitSize(config.screenHeightDp, config.screenWidthDp))) {
handleInfoChange(display);
}
}
@Override
public final void onLowMemory() {
}
public final void onLowMemory() { }
public void setPriorityListener(DisplayInfoChangeListener listener) {
mPriorityListener = listener;
@@ -262,7 +263,8 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
}
@AnyThread
private void handleInfoChange(Display display) {
@VisibleForTesting
public void handleInfoChange(Display display) {
WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(mContext);
Info oldInfo = mInfo;
@@ -295,6 +297,9 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
FileLog.w(TAG,
"(CHANGE_SUPPORTED_BOUNDS) perDisplayBounds: " + newInfo.mPerDisplayBounds);
}
if (newInfo.mIsTaskbarPinned != oldInfo.mIsTaskbarPinned) {
change |= CHANGE_TASKBAR_PINNING;
}
if (DEBUG) {
Log.d(TAG, "handleInfoChange - change: " + getChangeFlagsString(change));
}
@@ -334,7 +339,10 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
// WindowBounds
public final WindowBounds realBounds;
public final Set<WindowBounds> supportedBounds = new ArraySet<>();
private final ArrayMap<CachedDisplayInfo, List<WindowBounds>> mPerDisplayBounds = new ArrayMap<>();
private final ArrayMap<CachedDisplayInfo, List<WindowBounds>> mPerDisplayBounds =
new ArrayMap<>();
private final boolean mIsTaskbarPinned;
public Info(Context displayInfoContext) {
/* don't need system overrides for external displays */
@@ -343,8 +351,8 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
// Used for testing
public Info(Context displayInfoContext,
WindowManagerProxy wmProxy,
Map<CachedDisplayInfo, List<WindowBounds>> perDisplayBoundsCache) {
WindowManagerProxy wmProxy,
Map<CachedDisplayInfo, List<WindowBounds>> perDisplayBoundsCache) {
CachedDisplayInfo displayInfo = wmProxy.getDisplayInfo(displayInfoContext);
normalizedDisplayInfo = displayInfo.normalize();
rotation = displayInfo.rotation;
@@ -392,6 +400,27 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
Log.d(TAG, "normalizedDisplayInfo: " + normalizedDisplayInfo);
Log.d(TAG, "perDisplayBounds: " + mPerDisplayBounds);
}
mIsTaskbarPinned = LauncherPrefs.get(displayInfoContext).get(TASKBAR_PINNING);
}
/**
* Returns whether taskbar is transient.
*/
public boolean isTransientTaskbar() {
if (navigationMode != NavigationMode.NO_BUTTON) {
return false;
}
if (Utilities.isRunningInTestHarness()) {
// TODO(b/258604917): Once ENABLE_TASKBAR_PINNING is enabled, remove usage of
// sTransientTaskbarStatusForTests and update test to directly
// toggle shared preference to switch transient taskbar on/off.
return sTransientTaskbarStatusForTests;
}
if (false) {
return !mIsTaskbarPinned;
}
return true;
}
/**
@@ -401,6 +430,11 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
return smallestSizeDp(bounds) >= MIN_TABLET_WIDTH;
}
/** Getter for {@link #navigationMode} to allow mocking. */
public NavigationMode getNavigationMode() {
return navigationMode;
}
/**
* Returns smallest size in dp for given bounds.
*/
@@ -422,7 +456,6 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
/**
* Returns the given binary flags as a human-readable string.
*
* @see #CHANGE_ALL
*/
public String getChangeFlagsString(int change) {
@@ -432,6 +465,7 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
appendFlag(result, change, CHANGE_DENSITY, "CHANGE_DENSITY");
appendFlag(result, change, CHANGE_SUPPORTED_BOUNDS, "CHANGE_SUPPORTED_BOUNDS");
appendFlag(result, change, CHANGE_NAVIGATION_MODE, "CHANGE_NAVIGATION_MODE");
appendFlag(result, change, CHANGE_TASKBAR_PINNING, "CHANGE_TASKBAR_VARIANT");
return result.toString();
}
@@ -446,9 +480,11 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
pw.println(" fontScale=" + info.fontScale);
pw.println(" densityDpi=" + info.densityDpi);
pw.println(" navigationMode=" + info.navigationMode.name());
pw.println(" isTaskbarPinned=" + info.mIsTaskbarPinned);
pw.println(" currentSize=" + info.currentSize);
info.mPerDisplayBounds.forEach((key, value) -> pw.println(
" perDisplayBounds - " + key + ": " + value));
pw.println(" isTransientTaskbar=" + info.isTransientTaskbar());
}
/**
@@ -464,10 +500,8 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PortraitSize that = (PortraitSize) o;
return width == that.width && height == that.height;
}

View File

@@ -23,12 +23,14 @@ import kotlin.math.max
class IconSizeSteps(res: Resources) {
private val steps: List<Int>
val minimumIconLabelSize: Int
init {
steps =
res.obtainTypedArray(R.array.icon_size_steps).use {
(0 until it.length()).map { step -> it.getDimensionOrThrow(step).toInt() }.sorted()
}
minimumIconLabelSize = res.getDimensionPixelSize(R.dimen.minimum_icon_label_size)
}
fun minimumIconSize(): Int = steps[0]
@@ -37,11 +39,15 @@ class IconSizeSteps(res: Resources) {
return steps[max(0, getIndexForIconSize(iconSizePx) - 1)]
}
fun getIconSmallerThan(cellWidth: Int): Int {
return steps.lastOrNull { it <= cellWidth } ?: steps[0]
fun getIconSmallerThan(cellSize: Int): Int {
return steps.lastOrNull { it <= cellSize } ?: steps[0]
}
private fun getIndexForIconSize(iconSizePx: Int): Int {
return max(0, steps.indexOfFirst { iconSizePx <= it })
}
companion object {
internal const val TEXT_STEP = 1
}
}

View File

@@ -18,7 +18,6 @@ package com.android.launcher3.util.window;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_HEIGHT;
import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
@@ -136,7 +135,7 @@ public class WindowManagerProxy implements ResourceBasedOverride {
*/
@TargetApi(Build.VERSION_CODES.R)
public WindowInsets normalizeWindowInsets(Context context, WindowInsets oldInsets,
Rect outInsets) {
Rect outInsets) {
if (!Utilities.ATLEAST_R || !mTaskbarDrawnInProcess) {
outInsets.set(oldInsets.getSystemWindowInsetLeft(), oldInsets.getSystemWindowInsetTop(),
oldInsets.getSystemWindowInsetRight(), oldInsets.getSystemWindowInsetBottom());
@@ -156,10 +155,10 @@ public class WindowManagerProxy implements ResourceBasedOverride {
int bottomNav = isTablet
? 0
: (isPortrait
? getDimenByName(systemRes, NAVBAR_HEIGHT)
: (isGesture
? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE)
: 0));
? getDimenByName(systemRes, NAVBAR_HEIGHT)
: (isGesture
? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE)
: 0));
Insets newNavInsets = Insets.of(navInsets.left, navInsets.top, navInsets.right, bottomNav);
insetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets);
insetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(), newNavInsets);
@@ -205,7 +204,7 @@ public class WindowManagerProxy implements ResourceBasedOverride {
* Returns a list of possible WindowBounds for the display keyed on the 4 surface rotations
*/
protected List<WindowBounds> estimateWindowBounds(Context context,
CachedDisplayInfo displayInfo) {
CachedDisplayInfo displayInfo) {
int densityDpi = context.getResources().getConfiguration().densityDpi;
int rotation = displayInfo.rotation;
Rect safeCutout = displayInfo.cutout;
@@ -236,14 +235,14 @@ public class WindowManagerProxy implements ResourceBasedOverride {
navBarHeightPortrait = isTablet
? (mTaskbarDrawnInProcess
? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
: getDimenByName(systemRes, NAVBAR_HEIGHT);
navBarHeightLandscape = isTablet
? (mTaskbarDrawnInProcess
? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
: (isTabletOrGesture
? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE) : 0);
? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE) : 0);
navbarWidthLandscape = isTabletOrGesture
? 0
: getDimenByName(systemRes, NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
@@ -338,6 +337,20 @@ public class WindowManagerProxy implements ResourceBasedOverride {
return new CachedDisplayInfo(size, rotation, cutoutRect);
}
/**
* Returns bounds of the display associated with the context, or bounds of DEFAULT_DISPLAY
* if the context isn't associated with a display.
*/
public Rect getCurrentBounds(Context displayInfoContext) {
Resources res = displayInfoContext.getResources();
Configuration config = res.getConfiguration();
float screenWidth = config.screenWidthDp * res.getDisplayMetrics().density;
float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
return new Rect(0, 0, (int) screenWidth, (int) screenHeight);
}
/**
* Returns rotation of the display associated with the context, or rotation of DEFAULT_DISPLAY
* if the context isn't associated with a display.