mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-27 15:26:58 +00:00
Add Support for big screen (#4461)
* Initial Support for big screen * Fixed regressions * Don't recalculate when device isTablet
This commit is contained in:
@@ -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" />
|
||||
|
||||
119
lawnchair/res/layout/widgets_two_pane_sheet_paged_view.xml
Normal file
119
lawnchair/res/layout/widgets_two_pane_sheet_paged_view.xml
Normal 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>
|
||||
68
lawnchair/res/layout/widgets_two_pane_sheet_recyclerview.xml
Normal file
68
lawnchair/res/layout/widgets_two_pane_sheet_recyclerview.xml
Normal 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>
|
||||
23
lawnchair/res/layout/workspace_screen.xml
Normal file
23
lawnchair/res/layout/workspace_screen.xml
Normal 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" />
|
||||
23
lawnchair/res/layout/workspace_screen_foldable.xml
Normal file
23
lawnchair/res/layout/workspace_screen_foldable.xml
Normal 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" />
|
||||
@@ -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>
|
||||
|
||||
87
lawnchair/res/xml/default_workspace_4x4.xml
Normal file
87
lawnchair/res/xml/default_workspace_4x4.xml
Normal 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>
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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}" +
|
||||
")"
|
||||
}
|
||||
}
|
||||
182
src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
Normal file
182
src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
Normal 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}" +
|
||||
")"
|
||||
}
|
||||
}
|
||||
@@ -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}" +
|
||||
")"
|
||||
}
|
||||
}
|
||||
@@ -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}" +
|
||||
")"
|
||||
}
|
||||
}
|
||||
94
src/com/android/launcher3/responsive/ResponsiveSpecGroup.kt
Normal file
94
src/com/android/launcher3/responsive/ResponsiveSpecGroup.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
137
src/com/android/launcher3/responsive/ResponsiveSpecsProvider.kt
Normal file
137
src/com/android/launcher3/responsive/ResponsiveSpecsProvider.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
69
src/com/android/launcher3/util/CellContentDimensions.kt
Normal file
69
src/com/android/launcher3/util/CellContentDimensions.kt
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user