Snap for 9586404 from daf057472c to udc-release
Change-Id: I39bb360f87525b3e7fd74d6b0d33a7d8d73d2e16
This commit is contained in:
@@ -40,6 +40,7 @@
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<!-- for rotating surface by arbitrary degree -->
|
||||
<uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
|
||||
<uses-permission android:name="android.permission.WAKEUP_SURFACE_FLINGER" />
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?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.
|
||||
-->
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/mock_app_icon" />
|
||||
<corners android:radius="@dimen/gesture_tutorial_hotseat_icon_corner_radius" />
|
||||
</shape>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?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.
|
||||
-->
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/gesture_home_tutorial_background" />
|
||||
<corners android:radius="@dimen/gesture_tutorial_hotseat_icon_corner_radius" />
|
||||
</shape>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?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.
|
||||
-->
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/mock_search_bar" />
|
||||
<corners android:radius="@dimen/gesture_tutorial_hotseat_search_corner_radius" />
|
||||
</shape>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?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.
|
||||
-->
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/gesture_tutorial_fake_previous_task_view_color" />
|
||||
<corners android:radius="@dimen/gesture_tutorial_small_task_view_corner_radius" />
|
||||
</shape>
|
||||
@@ -24,54 +24,50 @@
|
||||
android:paddingStart="56dp"
|
||||
android:paddingEnd="56dp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_1"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintVertical_chainStyle="spread_inside"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/hotseat_icon_2"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_2"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toBottomOf="@id/hotseat_icon_1"
|
||||
app:layout_constraintBottom_toTopOf="@id/hotseat_icon_3"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_3"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toBottomOf="@id/hotseat_icon_2"
|
||||
app:layout_constraintBottom_toTopOf="@id/hotseat_icon_4"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_4"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toBottomOf="@id/hotseat_icon_3"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
||||
@@ -22,84 +22,77 @@
|
||||
android:paddingStart="@dimen/gesture_tutorial_hotseat_padding_start_end"
|
||||
android:paddingEnd="@dimen/gesture_tutorial_hotseat_padding_start_end">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_search_bar"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
|
||||
android:background="@drawable/hotseat_search_bar"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_search_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_search_bar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_1"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_1"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/hotseat_search_bar"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_2"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_2"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/hotseat_icon_1"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_3"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_3"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/hotseat_icon_2"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_4"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_4"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/hotseat_icon_3"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_5"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_5"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
||||
@@ -57,33 +57,34 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
|
||||
<View
|
||||
android:id="@+id/top_task_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="invisible"
|
||||
android:layout_marginBottom="@dimen/gesture_tutorial_multi_row_task_view_spacing"
|
||||
android:background="@drawable/top_task_view"
|
||||
android:clipToOutline="true"
|
||||
android:visibility="invisible"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_small_task_view_corner_radius"
|
||||
app:layout_constraintVertical_chainStyle="spread"
|
||||
app:layout_constraintTop_toTopOf="@id/full_task_view"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottom_task_view"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
app:layout_constraintTop_toTopOf="@id/full_task_view" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/bottom_task_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/top_task_view"
|
||||
android:clipToOutline="true"
|
||||
android:visibility="invisible"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_small_task_view_corner_radius"
|
||||
app:layout_constraintTop_toBottomOf="@id/top_task_view"
|
||||
app:layout_constraintBottom_toBottomOf="@id/full_task_view"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
app:layout_constraintTop_toBottomOf="@id/top_task_view" />
|
||||
|
||||
</com.android.quickstep.interaction.AnimatedTaskView>
|
||||
|
||||
|
||||
@@ -8,67 +8,62 @@
|
||||
android:paddingStart="26dp"
|
||||
android:paddingEnd="26dp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_1"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_2"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_2"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/hotseat_icon_1"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_3"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_3"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/hotseat_icon_2"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_4"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_4"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/hotseat_icon_3"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
|
||||
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
|
||||
android:background="@drawable/hotseat_search_bar"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_search_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_search_bar"
|
||||
app:layout_constraintTop_toBottomOf="@id/hotseat_icon_1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
@@ -22,84 +22,78 @@
|
||||
android:paddingStart="@dimen/gesture_tutorial_hotseat_padding_start_end"
|
||||
android:paddingEnd="@dimen/gesture_tutorial_hotseat_padding_start_end">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_search_bar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
|
||||
android:background="@drawable/hotseat_search_bar"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_search_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_search_bar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_1"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintTop_toBottomOf="@id/hotseat_search_bar"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_2"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_2"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toBottomOf="@id/hotseat_search_bar"
|
||||
app:layout_constraintStart_toEndOf="@id/hotseat_icon_1"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_3"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_3"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toBottomOf="@id/hotseat_search_bar"
|
||||
app:layout_constraintStart_toEndOf="@id/hotseat_icon_2"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_4"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_4"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toBottomOf="@id/hotseat_search_bar"
|
||||
app:layout_constraintStart_toEndOf="@id/hotseat_icon_3"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_5"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_5"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
|
||||
android:background="@drawable/hotseat_icon"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardBackgroundColor="@color/mock_app_icon"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toBottomOf="@id/hotseat_search_bar"
|
||||
app:layout_constraintStart_toEndOf="@id/hotseat_icon_4"
|
||||
|
||||
@@ -57,29 +57,29 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
|
||||
<View
|
||||
android:id="@+id/top_task_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="@dimen/gesture_tutorial_multi_row_task_view_spacing"
|
||||
android:background="@drawable/top_task_view"
|
||||
android:clipToOutline="true"
|
||||
android:visibility="invisible"
|
||||
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_small_task_view_corner_radius"
|
||||
app:cardElevation="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottom_task_view"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/full_task_view"
|
||||
app:layout_constraintVertical_chainStyle="spread" />
|
||||
app:layout_constraintTop_toTopOf="@id/full_task_view" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/bottom_task_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/top_task_view"
|
||||
android:clipToOutline="true"
|
||||
android:visibility="invisible"
|
||||
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_small_task_view_corner_radius"
|
||||
app:cardElevation="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/full_task_view"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -99,13 +99,13 @@
|
||||
android:background="@drawable/gesture_tutorial_ripple" />
|
||||
|
||||
<include
|
||||
layout="@layout/gesture_tutorial_tablet_mock_taskbar"
|
||||
android:id="@+id/gesture_tutorial_fake_taskbar_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/gesture_tutorial_mock_taskbar_height"
|
||||
layout="@layout/gesture_tutorial_tablet_mock_taskbar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentEnd="true" />
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginBottom="@dimen/gesture_tutorial_taskbar_margin_bottom" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gesture_tutorial_edge_gesture_video"
|
||||
|
||||
@@ -21,70 +21,64 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="70dp"
|
||||
android:paddingStart="26dp"
|
||||
android:paddingEnd="26dp"
|
||||
android:background="@color/gesture_home_tutorial_swipe_up_rect">
|
||||
android:paddingEnd="26dp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_1"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon_home"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardBackgroundColor="@color/gesture_home_tutorial_background"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardElevation="0dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_2"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_2"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon_home"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardBackgroundColor="@color/gesture_home_tutorial_background"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardElevation="0dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_3"
|
||||
app:layout_constraintStart_toEndOf="@id/hotseat_icon_1"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_3"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon_home"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardBackgroundColor="@color/gesture_home_tutorial_background"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardElevation="0dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_4"
|
||||
app:layout_constraintStart_toEndOf="@id/hotseat_icon_2"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:id="@+id/hotseat_icon_4"
|
||||
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
|
||||
android:background="@drawable/hotseat_icon_home"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardBackgroundColor="@color/gesture_home_tutorial_background"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
|
||||
app:cardElevation="0dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/hotseat_icon_3"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
|
||||
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
|
||||
android:background="@drawable/hotseat_icon_home"
|
||||
android:clipToOutline="true"
|
||||
|
||||
app:cardBackgroundColor="@color/gesture_home_tutorial_background"
|
||||
app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_search_corner_radius"
|
||||
app:cardElevation="0dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/hotseat_icon_1" />
|
||||
|
||||
+8
@@ -39,6 +39,7 @@ import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
@@ -62,6 +63,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
|
||||
private static final long TRANSLATION_ANIM_MIN_DURATION_MS = 80;
|
||||
private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
|
||||
|
||||
private final VibratorWrapper mVibratorWrapper;
|
||||
private final RecentsView mRecentsView;
|
||||
private final MotionPauseDetector mMotionPauseDetector;
|
||||
private final float mMotionPauseMinDisplacement;
|
||||
@@ -82,6 +84,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
|
||||
mRecentsView = l.getOverviewPanel();
|
||||
mMotionPauseDetector = new MotionPauseDetector(l);
|
||||
mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
|
||||
mVibratorWrapper = VibratorWrapper.INSTANCE.get(l.getApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -188,6 +191,11 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
|
||||
// need to manually set the duration to a reasonable value.
|
||||
animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher, true /* isToState */));
|
||||
}
|
||||
if (FeatureFlags.ENABLE_HAPTICS_ALL_APPS.get() &&
|
||||
((mFromState == NORMAL && mToState == ALL_APPS)
|
||||
|| (mFromState == ALL_APPS && mToState == NORMAL)) && isFling) {
|
||||
mVibratorWrapper.vibrateForDragBump();
|
||||
}
|
||||
}
|
||||
|
||||
private void onMotionPauseDetected() {
|
||||
|
||||
@@ -30,7 +30,6 @@ import android.view.ViewOutlineProvider;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
@@ -46,8 +45,8 @@ import java.util.ArrayList;
|
||||
public class AnimatedTaskView extends ConstraintLayout {
|
||||
|
||||
private View mFullTaskView;
|
||||
private CardView mTopTaskView;
|
||||
private CardView mBottomTaskView;
|
||||
private View mTopTaskView;
|
||||
private View mBottomTaskView;
|
||||
|
||||
private ViewOutlineProvider mTaskViewOutlineProvider = null;
|
||||
private final Rect mTaskViewAnimatedRect = new Rect();
|
||||
@@ -185,8 +184,6 @@ public class AnimatedTaskView extends ConstraintLayout {
|
||||
|
||||
void setFakeTaskViewFillColor(@ColorInt int colorResId) {
|
||||
mFullTaskView.setBackgroundColor(colorResId);
|
||||
mTopTaskView.setCardBackgroundColor(colorResId);
|
||||
mBottomTaskView.setCardBackgroundColor(colorResId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -61,10 +61,10 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll
|
||||
|
||||
@Override
|
||||
protected int getMockAppTaskLayoutResId() {
|
||||
return mTutorialFragment.isLargeScreen()
|
||||
? R.layout.gesture_tutorial_tablet_mock_webpage
|
||||
: ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
|
||||
? R.layout.swipe_up_gesture_tutorial_shape
|
||||
return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
|
||||
? R.layout.swipe_up_gesture_tutorial_shape
|
||||
: mTutorialFragment.isLargeScreen()
|
||||
? R.layout.gesture_tutorial_tablet_mock_webpage
|
||||
: R.layout.gesture_tutorial_mock_webpage;
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +154,6 @@ abstract class TutorialController implements BackGestureAttemptCallback,
|
||||
|
||||
mFeedbackTitleView.setText(getIntroductionTitle());
|
||||
mFeedbackSubtitleView.setText(getIntroductionSubtitle());
|
||||
mSkipButton.setVisibility(GONE);
|
||||
}
|
||||
|
||||
mTitleViewCallback = () -> mFeedbackTitleView.sendAccessibilityEvent(
|
||||
|
||||
@@ -355,6 +355,12 @@ public class GroupedTaskView extends TaskView {
|
||||
mSnapshotView2.setSplashAlpha(mTaskThumbnailSplashAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refreshTaskThumbnailSplash() {
|
||||
super.refreshTaskThumbnailSplash();
|
||||
mSnapshotView2.refreshSplashView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets visibility for thumbnails and associated elements (DWB banners).
|
||||
* IconView is unaffected.
|
||||
|
||||
@@ -92,6 +92,7 @@ public class LauncherRecentsView extends RecentsView<QuickstepLauncher, Launcher
|
||||
|
||||
@Override
|
||||
public void onTaskIconChanged(int taskId) {
|
||||
super.onTaskIconChanged(taskId);
|
||||
// If Launcher needs to return to split select state, do it now, after the icon has updated.
|
||||
if (mActivity.hasPendingSplitSelectInfo()) {
|
||||
PendingSplitSelectInfo recoveryData = mActivity.getPendingSplitSelectInfo();
|
||||
|
||||
@@ -956,6 +956,14 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskIconChanged(int taskId) {
|
||||
TaskView taskView = getTaskViewByTaskId(taskId);
|
||||
if (taskView != null) {
|
||||
taskView.refreshTaskThumbnailSplash();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the thumbnail of the task.
|
||||
* @param refreshNow Refresh immediately if it's true.
|
||||
|
||||
@@ -342,10 +342,11 @@ public class TaskThumbnailView extends View {
|
||||
|
||||
// Draw splash above thumbnail to hide inconsistencies in rotation and aspect ratios.
|
||||
if (shouldShowSplashView()) {
|
||||
// Always draw background for hiding inconsistencies, even if splash view is not yet
|
||||
// loaded (which can happen as task icons are loaded asynchronously in the background)
|
||||
canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadius,
|
||||
cornerRadius, mSplashBackgroundPaint);
|
||||
if (mSplashView != null) {
|
||||
canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadius,
|
||||
cornerRadius, mSplashBackgroundPaint);
|
||||
|
||||
mSplashView.layout((int) x, (int) (y + 1), (int) width, (int) height - 1);
|
||||
mSplashView.draw(canvas);
|
||||
}
|
||||
@@ -375,6 +376,13 @@ public class TaskThumbnailView extends View {
|
||||
|| isThumbnailRotationDifferentFromTask();
|
||||
}
|
||||
|
||||
protected void refreshSplashView() {
|
||||
if (mTask != null) {
|
||||
updateSplashView(mTask.icon);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSplashView(Drawable icon) {
|
||||
if (icon == null || icon.getConstantState() == null) {
|
||||
mSplashViewDrawable = null;
|
||||
|
||||
@@ -1215,6 +1215,10 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
mSnapshotView.setSplashAlpha(mTaskThumbnailSplashAlpha);
|
||||
}
|
||||
|
||||
protected void refreshTaskThumbnailSplash() {
|
||||
mSnapshotView.refreshSplashView();
|
||||
}
|
||||
|
||||
private void setSplitSelectTranslationX(float x) {
|
||||
mSplitSelectTranslationX = x;
|
||||
applyTranslationX();
|
||||
|
||||
@@ -19,7 +19,7 @@ import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.launcher3.DeviceProfileBaseTest
|
||||
import com.android.launcher3.FakeInvariantDeviceProfileTest
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
|
||||
import com.android.quickstep.views.TaskView.FullscreenDrawParams
|
||||
@@ -36,7 +36,7 @@ import org.mockito.Mockito.mock
|
||||
/** Test for FullscreenDrawParams class. */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class FullscreenDrawParamsTest : DeviceProfileBaseTest() {
|
||||
class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() {
|
||||
|
||||
private val TASK_SCALE = 0.7f
|
||||
private var mThumbnailData: ThumbnailData = mock(ThumbnailData::class.java)
|
||||
|
||||
@@ -18,7 +18,7 @@ package com.android.quickstep
|
||||
import android.graphics.Rect
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.launcher3.DeviceProfileBaseTest
|
||||
import com.android.launcher3.FakeInvariantDeviceProfileTest
|
||||
import com.android.launcher3.util.WindowBounds
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
@@ -26,7 +26,7 @@ import org.junit.runner.RunWith
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class HotseatWidthCalculationTest : DeviceProfileBaseTest() {
|
||||
class HotseatWidthCalculationTest : FakeInvariantDeviceProfileTest() {
|
||||
|
||||
/**
|
||||
* This is a case when after setting the hotseat, the space needs to be recalculated but it
|
||||
|
||||
@@ -193,7 +193,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
|
||||
mLauncher.goHome().switchToOverview().getCurrentTask()
|
||||
.tapMenu()
|
||||
.tapSplitMenuItem()
|
||||
.getTestActivityTask(2)
|
||||
.getCurrentTask()
|
||||
.open();
|
||||
}
|
||||
|
||||
|
||||
@@ -224,6 +224,21 @@
|
||||
<attr name="alignOnIcon" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
<!-- Responsive grids attributes -->
|
||||
<declare-styleable name="WorkspaceSpec">
|
||||
<attr name="specType" format="integer">
|
||||
<enum name="height" value="0" />
|
||||
<enum name="width" value="1" />
|
||||
</attr>
|
||||
<attr name="maxAvailableSize" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="SpecSize">
|
||||
<attr name="fixedSize" format="dimension" />
|
||||
<attr name="ofAvailableSpace" format="float" />
|
||||
<attr name="ofRemainderSpace" format="float" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ProfileDisplayOption">
|
||||
<attr name="name" />
|
||||
<attr name="minWidthDps" format="float" />
|
||||
|
||||
@@ -501,7 +501,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
}
|
||||
|
||||
private boolean isTwoPanelEnabled() {
|
||||
return mLauncher.mDeviceProfile.isTwoPanels;
|
||||
return !FOLDABLE_SINGLE_PAGE.get() && mLauncher.mDeviceProfile.isTwoPanels;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -663,8 +663,9 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
|
||||
// Inflate the cell layout, but do not add it automatically so that we can get the newly
|
||||
// created CellLayout.
|
||||
DeviceProfile dp = mLauncher.getDeviceProfile();
|
||||
CellLayout newScreen;
|
||||
if (FOLDABLE_SINGLE_PAGE.get() && isTwoPanelEnabled()) {
|
||||
if (FOLDABLE_SINGLE_PAGE.get() && dp.isTwoPanels) {
|
||||
newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
|
||||
R.layout.workspace_screen_foldable, this, false /* attachToRoot */);
|
||||
} else {
|
||||
|
||||
@@ -32,6 +32,7 @@ import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
|
||||
import android.animation.Animator;
|
||||
import android.animation.Animator.AnimatorListener;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.View;
|
||||
@@ -47,17 +48,21 @@ import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.anim.PropertySetter;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.statemanager.StateManager.StateHandler;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.touch.AllAppsSwipeController;
|
||||
import com.android.launcher3.util.MultiPropertyFactory;
|
||||
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.util.VibratorWrapper;
|
||||
import com.android.launcher3.views.ScrimView;
|
||||
|
||||
/**
|
||||
@@ -78,6 +83,8 @@ public class AllAppsTransitionController
|
||||
private static final int REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS = 200;
|
||||
|
||||
private static final float NAV_BAR_COLOR_FORCE_UPDATE_THRESHOLD = 0.1f;
|
||||
private static final float SWIPE_DRAG_COMMIT_THRESHOLD =
|
||||
1 - AllAppsSwipeController.ALL_APPS_STATE_TRANSITION_MANUAL;
|
||||
|
||||
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
|
||||
new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
|
||||
@@ -181,6 +188,7 @@ public class AllAppsTransitionController
|
||||
private boolean mIsTablet;
|
||||
|
||||
private boolean mHasScaleEffect;
|
||||
private final VibratorWrapper mVibratorWrapper;
|
||||
|
||||
public AllAppsTransitionController(Launcher l) {
|
||||
mLauncher = l;
|
||||
@@ -193,6 +201,7 @@ public class AllAppsTransitionController
|
||||
|
||||
setShiftRange(dp.allAppsShiftRange);
|
||||
mLauncher.addOnDeviceProfileChangeListener(this);
|
||||
mVibratorWrapper = VibratorWrapper.INSTANCE.get(mLauncher.getApplicationContext());
|
||||
}
|
||||
|
||||
public float getShiftRange() {
|
||||
@@ -311,6 +320,11 @@ public class AllAppsTransitionController
|
||||
/**
|
||||
* Creates an animation which updates the vertical transition progress and updates all the
|
||||
* dependent UI using various animation events
|
||||
*
|
||||
* This method also dictates where along the progress the haptics should be played. As the user
|
||||
* scrolls up from workspace or down from AllApps, a drag haptic is being played until the
|
||||
* commit point where it plays a commit haptic. Where we play the haptics differs when going
|
||||
* from workspace -> allApps and vice versa.
|
||||
*/
|
||||
@Override
|
||||
public void setStateWithAnimation(LauncherState toState,
|
||||
@@ -339,6 +353,20 @@ public class AllAppsTransitionController
|
||||
});
|
||||
}
|
||||
|
||||
if(FeatureFlags.ENABLE_HAPTICS_ALL_APPS.get() && config.userControlled
|
||||
&& Utilities.ATLEAST_S) {
|
||||
if (toState == ALL_APPS) {
|
||||
builder.addOnFrameListener(
|
||||
new VibrationAnimatorUpdateListener(this, mVibratorWrapper,
|
||||
SWIPE_DRAG_COMMIT_THRESHOLD, 1));
|
||||
} else {
|
||||
builder.addOnFrameListener(
|
||||
new VibrationAnimatorUpdateListener(this, mVibratorWrapper,
|
||||
0, SWIPE_DRAG_COMMIT_THRESHOLD));
|
||||
}
|
||||
builder.addEndListener(mVibratorWrapper::cancelVibrate);
|
||||
}
|
||||
|
||||
float targetProgress = toState.getVerticalProgress(mLauncher);
|
||||
if (Float.compare(mProgress, targetProgress) == 0) {
|
||||
setAlphas(toState, config, builder);
|
||||
@@ -356,7 +384,7 @@ public class AllAppsTransitionController
|
||||
|
||||
setAlphas(toState, config, builder);
|
||||
|
||||
if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL)) {
|
||||
if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL) && !(Utilities.ATLEAST_S)) {
|
||||
mLauncher.getAppsView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
|
||||
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
|
||||
}
|
||||
@@ -494,4 +522,45 @@ public class AllAppsTransitionController
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This VibrationAnimatorUpdateListener class takes in four parameters, a controller, start
|
||||
* threshold, end threshold, and a Vibrator wrapper. We use the progress given by the controller
|
||||
* as it gives an accurate progress that dictates where the vibrator should vibrate.
|
||||
* Note: once the user begins a gesture and does the commit haptic, there should not be anymore
|
||||
* haptics played for that gesture.
|
||||
*/
|
||||
private static class VibrationAnimatorUpdateListener implements
|
||||
ValueAnimator.AnimatorUpdateListener {
|
||||
private final VibratorWrapper mVibratorWrapper;
|
||||
private final AllAppsTransitionController mController;
|
||||
private final float mStartThreshold;
|
||||
private final float mEndThreshold;
|
||||
private boolean mHasCommitted;
|
||||
|
||||
VibrationAnimatorUpdateListener(AllAppsTransitionController controller,
|
||||
VibratorWrapper vibratorWrapper, float startThreshold,
|
||||
float endThreshold) {
|
||||
mController = controller;
|
||||
mVibratorWrapper = vibratorWrapper;
|
||||
mStartThreshold = startThreshold;
|
||||
mEndThreshold = endThreshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
if (mHasCommitted) {
|
||||
return;
|
||||
}
|
||||
float currentProgress =
|
||||
AllAppsTransitionController.ALL_APPS_PROGRESS.get(mController);
|
||||
if (currentProgress > mStartThreshold && currentProgress < mEndThreshold) {
|
||||
mVibratorWrapper.vibrateForDragTexture();
|
||||
} else if (!(currentProgress == 0 || currentProgress == 1)) {
|
||||
// This check guards against committing at the location of the start of the gesture
|
||||
mVibratorWrapper.vibrateForDragCommit();
|
||||
mHasCommitted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,6 +374,8 @@ public final class FeatureFlags {
|
||||
"ENABLE_LAUNCH_FROM_STAGED_APP", true,
|
||||
"Enable the ability to tap a staged app during split select to launch it in full screen"
|
||||
);
|
||||
public static final BooleanFlag ENABLE_HAPTICS_ALL_APPS = getDebugFlag(
|
||||
"ENABLE_HAPTICS_ALL_APPS", false, "Enables haptics opening/closing All apps");
|
||||
|
||||
public static final BooleanFlag ENABLE_FORCED_MONO_ICON = getDebugFlag(
|
||||
"ENABLE_FORCED_MONO_ICON", false,
|
||||
|
||||
@@ -129,10 +129,7 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController {
|
||||
Interpolators.clampToProgress(
|
||||
Interpolators.mapToProgress(EMPHASIZED_DECELERATE, 0.4f, 1f),
|
||||
ALL_APPS_STATE_TRANSITION_ATOMIC, 1f);
|
||||
public static final Interpolator ALL_APPS_VERTICAL_PROGRESS_MANUAL =
|
||||
Interpolators.clampToProgress(
|
||||
Interpolators.mapToProgress(LINEAR, ALL_APPS_STATE_TRANSITION_MANUAL, 1f),
|
||||
ALL_APPS_STATE_TRANSITION_MANUAL, 1f);
|
||||
public static final Interpolator ALL_APPS_VERTICAL_PROGRESS_MANUAL = LINEAR;
|
||||
|
||||
// --------
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.content.res.XmlResourceParser
|
||||
import android.util.AttributeSet
|
||||
import kotlin.IntArray
|
||||
|
||||
/**
|
||||
* This class is a helper that can be subclassed in tests to provide a way to parse attributes
|
||||
* correctly.
|
||||
*/
|
||||
open class ResourceHelper(private val context: Context, private val specsFileId: Int) {
|
||||
open fun getXml(): XmlResourceParser {
|
||||
return context.resources.getXml(specsFileId)
|
||||
}
|
||||
|
||||
open fun obtainStyledAttributes(attrs: AttributeSet, styleId: IntArray): TypedArray {
|
||||
return context.obtainStyledAttributes(attrs, styleId)
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ package com.android.launcher3.util;
|
||||
|
||||
import static android.os.VibrationEffect.createPredefined;
|
||||
import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
|
||||
@@ -28,12 +27,17 @@ import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.media.AudioAttributes;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
|
||||
@@ -52,6 +56,21 @@ public class VibratorWrapper {
|
||||
public static final VibrationEffect EFFECT_CLICK =
|
||||
createPredefined(VibrationEffect.EFFECT_CLICK);
|
||||
|
||||
private static final float DRAG_TEXTURE_SCALE = 0.03f;
|
||||
private static final float DRAG_COMMIT_SCALE = 0.5f;
|
||||
private static final float DRAG_BUMP_SCALE = 0.4f;
|
||||
private static final int DRAG_TEXTURE_EFFECT_SIZE = 200;
|
||||
|
||||
@Nullable
|
||||
private final VibrationEffect mDragEffect;
|
||||
@Nullable
|
||||
private final VibrationEffect mCommitEffect;
|
||||
@Nullable
|
||||
private final VibrationEffect mBumpEffect;
|
||||
|
||||
private long mLastDragTime;
|
||||
private final int mThresholdUntilNextDragCallMillis;
|
||||
|
||||
/**
|
||||
* Haptic when entering overview.
|
||||
*/
|
||||
@@ -62,7 +81,7 @@ public class VibratorWrapper {
|
||||
|
||||
private boolean mIsHapticFeedbackEnabled;
|
||||
|
||||
public VibratorWrapper(Context context) {
|
||||
private VibratorWrapper(Context context) {
|
||||
mVibrator = context.getSystemService(Vibrator.class);
|
||||
mHasVibrator = mVibrator.hasVibrator();
|
||||
if (mHasVibrator) {
|
||||
@@ -75,12 +94,88 @@ public class VibratorWrapper {
|
||||
}
|
||||
};
|
||||
resolver.registerContentObserver(Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED),
|
||||
false /* notifyForDescendents */, observer);
|
||||
false /* notifyForDescendants */, observer);
|
||||
} else {
|
||||
mIsHapticFeedbackEnabled = false;
|
||||
}
|
||||
|
||||
if (Utilities.ATLEAST_S && mVibrator.areAllPrimitivesSupported(
|
||||
VibrationEffect.Composition.PRIMITIVE_LOW_TICK)) {
|
||||
|
||||
// Drag texture, Commit, and Bump should only be used for premium phones.
|
||||
// Before using these haptics make sure check if the device can use it
|
||||
VibrationEffect.Composition dragEffect = VibrationEffect.startComposition();
|
||||
for (int i = 0; i < DRAG_TEXTURE_EFFECT_SIZE; i++) {
|
||||
dragEffect.addPrimitive(
|
||||
VibrationEffect.Composition.PRIMITIVE_LOW_TICK, DRAG_TEXTURE_SCALE);
|
||||
}
|
||||
mDragEffect = dragEffect.compose();
|
||||
mCommitEffect = VibrationEffect.startComposition().addPrimitive(
|
||||
VibrationEffect.Composition.PRIMITIVE_TICK, DRAG_COMMIT_SCALE).compose();
|
||||
mBumpEffect = VibrationEffect.startComposition().addPrimitive(
|
||||
VibrationEffect.Composition.PRIMITIVE_LOW_TICK, DRAG_BUMP_SCALE).compose();
|
||||
int primitiveDuration = mVibrator.getPrimitiveDurations(
|
||||
VibrationEffect.Composition.PRIMITIVE_LOW_TICK)[0];
|
||||
|
||||
mThresholdUntilNextDragCallMillis =
|
||||
DRAG_TEXTURE_EFFECT_SIZE * primitiveDuration + 100;
|
||||
} else {
|
||||
mDragEffect = null;
|
||||
mCommitEffect = null;
|
||||
mBumpEffect = null;
|
||||
mThresholdUntilNextDragCallMillis = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when the user swipes to/from all apps. This is meant to be used in between
|
||||
* long animation progresses so that it gives a dragging texture effect. For a better
|
||||
* experience, this should be used in combination with vibrateForDragCommit().
|
||||
*/
|
||||
public void vibrateForDragTexture() {
|
||||
if (mDragEffect == null) {
|
||||
return;
|
||||
}
|
||||
long currentTime = SystemClock.elapsedRealtime();
|
||||
long elapsedTimeSinceDrag = currentTime - mLastDragTime;
|
||||
if (elapsedTimeSinceDrag >= mThresholdUntilNextDragCallMillis) {
|
||||
vibrate(mDragEffect);
|
||||
mLastDragTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used when user reaches the commit threshold when swiping to/from from all apps.
|
||||
*/
|
||||
public void vibrateForDragCommit() {
|
||||
if (mCommitEffect != null) {
|
||||
vibrate(mCommitEffect);
|
||||
}
|
||||
// resetting dragTexture timestamp to be able to play dragTexture again
|
||||
mLastDragTime = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The bump haptic is used to be called at the end of a swipe and only if it the gesture is a
|
||||
* FLING going to/from all apps. Client can just call this method elsewhere just for the
|
||||
* effect.
|
||||
*/
|
||||
public void vibrateForDragBump() {
|
||||
if (mBumpEffect != null) {
|
||||
vibrate(mBumpEffect);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be used to cancel a haptic in case where the haptic shouldn't be vibrating. For
|
||||
* example, when no animation is happening but a vibrator happens to be vibrating still. Need
|
||||
* boolean parameter for {@link PendingAnimation#addEndListener(Consumer)}.
|
||||
*/
|
||||
public void cancelVibrate(boolean unused) {
|
||||
UI_HELPER_EXECUTOR.execute(mVibrator::cancel);
|
||||
// reset dragTexture timestamp to be able to play dragTexture again whenever cancelled
|
||||
mLastDragTime = 0;
|
||||
}
|
||||
private boolean isHapticFeedbackEnabled(ContentResolver resolver) {
|
||||
return Settings.System.getInt(resolver, HAPTIC_FEEDBACK_ENABLED, 0) == 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* 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.workspace
|
||||
|
||||
import android.content.res.TypedArray
|
||||
import android.content.res.XmlResourceParser
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.util.Xml
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.util.ResourceHelper
|
||||
import java.io.IOException
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
|
||||
private const val TAG = "WorkspaceSpecs"
|
||||
|
||||
class WorkspaceSpecs(resourceHelper: ResourceHelper) {
|
||||
object XmlTags {
|
||||
const val WORKSPACE_SPECS = "workspaceSpecs"
|
||||
|
||||
const val WORKSPACE_SPEC = "workspaceSpec"
|
||||
const val START_PADDING = "startPadding"
|
||||
const val END_PADDING = "endPadding"
|
||||
const val GUTTER = "gutter"
|
||||
const val CELL_SIZE = "cellSize"
|
||||
}
|
||||
|
||||
val workspaceHeightSpecList = mutableListOf<WorkspaceSpec>()
|
||||
val workspaceWidthSpecList = mutableListOf<WorkspaceSpec>()
|
||||
|
||||
init {
|
||||
try {
|
||||
val parser: XmlResourceParser = resourceHelper.getXml()
|
||||
val depth = parser.depth
|
||||
var type: Int
|
||||
while (
|
||||
(parser.next().also { type = it } != XmlPullParser.END_TAG ||
|
||||
parser.depth > depth) && type != XmlPullParser.END_DOCUMENT
|
||||
) {
|
||||
if (type == XmlPullParser.START_TAG && XmlTags.WORKSPACE_SPECS == parser.name) {
|
||||
val displayDepth = parser.depth
|
||||
while (
|
||||
(parser.next().also { type = it } != XmlPullParser.END_TAG ||
|
||||
parser.depth > displayDepth) && type != XmlPullParser.END_DOCUMENT
|
||||
) {
|
||||
if (
|
||||
type == XmlPullParser.START_TAG && XmlTags.WORKSPACE_SPEC == parser.name
|
||||
) {
|
||||
val attrs =
|
||||
resourceHelper.obtainStyledAttributes(
|
||||
Xml.asAttributeSet(parser),
|
||||
R.styleable.WorkspaceSpec
|
||||
)
|
||||
val maxAvailableSize =
|
||||
attrs.getDimensionPixelSize(
|
||||
R.styleable.WorkspaceSpec_maxAvailableSize,
|
||||
0
|
||||
)
|
||||
val specType =
|
||||
WorkspaceSpec.SpecType.values()[
|
||||
attrs.getInt(
|
||||
R.styleable.WorkspaceSpec_specType,
|
||||
WorkspaceSpec.SpecType.HEIGHT.ordinal
|
||||
)
|
||||
]
|
||||
attrs.recycle()
|
||||
|
||||
var startPadding: SizeSpec? = null
|
||||
var endPadding: SizeSpec? = null
|
||||
var gutter: SizeSpec? = null
|
||||
var cellSize: SizeSpec? = null
|
||||
|
||||
val limitDepth = parser.depth
|
||||
while (
|
||||
(parser.next().also { type = it } != XmlPullParser.END_TAG ||
|
||||
parser.depth > limitDepth) && type != XmlPullParser.END_DOCUMENT
|
||||
) {
|
||||
val attr: AttributeSet = Xml.asAttributeSet(parser)
|
||||
if (type == XmlPullParser.START_TAG) {
|
||||
when (parser.name) {
|
||||
XmlTags.START_PADDING -> {
|
||||
startPadding = SizeSpec(resourceHelper, attr)
|
||||
}
|
||||
XmlTags.END_PADDING -> {
|
||||
endPadding = SizeSpec(resourceHelper, attr)
|
||||
}
|
||||
XmlTags.GUTTER -> {
|
||||
gutter = SizeSpec(resourceHelper, attr)
|
||||
}
|
||||
XmlTags.CELL_SIZE -> {
|
||||
cellSize = SizeSpec(resourceHelper, attr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
startPadding == null ||
|
||||
endPadding == null ||
|
||||
gutter == null ||
|
||||
cellSize == null
|
||||
) {
|
||||
throw IllegalStateException(
|
||||
"All attributes in workspaceSpec must be defined"
|
||||
)
|
||||
}
|
||||
|
||||
val workspaceSpec =
|
||||
WorkspaceSpec(
|
||||
maxAvailableSize,
|
||||
specType,
|
||||
startPadding,
|
||||
endPadding,
|
||||
gutter,
|
||||
cellSize
|
||||
)
|
||||
if (workspaceSpec.isValid()) {
|
||||
if (workspaceSpec.specType == WorkspaceSpec.SpecType.HEIGHT)
|
||||
workspaceHeightSpecList.add(workspaceSpec)
|
||||
else workspaceWidthSpecList.add(workspaceSpec)
|
||||
} else {
|
||||
throw IllegalStateException("Invalid workspaceSpec found.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (workspaceWidthSpecList.isEmpty() || workspaceHeightSpecList.isEmpty()) {
|
||||
throw IllegalStateException(
|
||||
"WorkspaceSpecs is incomplete - " +
|
||||
"height list size = ${workspaceHeightSpecList.size}; " +
|
||||
"width list size = ${workspaceWidthSpecList.size}."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
parser.close()
|
||||
} catch (e: Exception) {
|
||||
when (e) {
|
||||
is IOException,
|
||||
is XmlPullParserException -> {
|
||||
throw RuntimeException("Failure parsing workspaces specs file.", e)
|
||||
}
|
||||
else -> throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class WorkspaceSpec(
|
||||
val maxAvailableSize: Int,
|
||||
val specType: SpecType,
|
||||
val startPadding: SizeSpec,
|
||||
val endPadding: SizeSpec,
|
||||
val gutter: SizeSpec,
|
||||
val cellSize: SizeSpec
|
||||
) {
|
||||
|
||||
enum class SpecType {
|
||||
HEIGHT,
|
||||
WIDTH
|
||||
}
|
||||
|
||||
fun isValid(): Boolean {
|
||||
if (maxAvailableSize <= 0) {
|
||||
Log.e(TAG, "WorkspaceSpec#isValid - maxAvailableSize <= 0")
|
||||
return false
|
||||
}
|
||||
|
||||
// All specs need to be individually valid
|
||||
if (!allSpecsAreValid()) {
|
||||
Log.e(TAG, "WorkspaceSpec#isValid - !allSpecsAreValid()")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun allSpecsAreValid(): Boolean =
|
||||
startPadding.isValid() && endPadding.isValid() && gutter.isValid() && cellSize.isValid()
|
||||
}
|
||||
|
||||
class SizeSpec(resourceHelper: ResourceHelper, attrs: AttributeSet) {
|
||||
val fixedSize: Float
|
||||
val ofAvailableSpace: Float
|
||||
val ofRemainderSpace: Float
|
||||
|
||||
init {
|
||||
val styledAttrs = resourceHelper.obtainStyledAttributes(attrs, R.styleable.SpecSize)
|
||||
|
||||
fixedSize = getValue(styledAttrs, R.styleable.SpecSize_fixedSize)
|
||||
ofAvailableSpace = getValue(styledAttrs, R.styleable.SpecSize_ofAvailableSpace)
|
||||
ofRemainderSpace = getValue(styledAttrs, R.styleable.SpecSize_ofRemainderSpace)
|
||||
|
||||
styledAttrs.recycle()
|
||||
}
|
||||
|
||||
private fun getValue(a: TypedArray, index: Int): Float {
|
||||
if (a.getType(index) == TypedValue.TYPE_DIMENSION) {
|
||||
return a.getDimensionPixelSize(index, 0).toFloat()
|
||||
} else if (a.getType(index) == TypedValue.TYPE_FLOAT) {
|
||||
return a.getFloat(index, 0f)
|
||||
}
|
||||
return 0f
|
||||
}
|
||||
|
||||
fun isValid(): Boolean {
|
||||
// All attributes are empty
|
||||
if (fixedSize <= 0f && ofAvailableSpace <= 0f && ofRemainderSpace <= 0f) {
|
||||
Log.e(TAG, "SizeSpec#isValid - all attributes are empty")
|
||||
return false
|
||||
}
|
||||
|
||||
// More than one attribute is filled
|
||||
val attrCount =
|
||||
(if (fixedSize > 0) 1 else 0) +
|
||||
(if (ofAvailableSpace > 0) 1 else 0) +
|
||||
(if (ofRemainderSpace > 0) 1 else 0)
|
||||
if (attrCount > 1) {
|
||||
Log.e(TAG, "SizeSpec#isValid - more than one attribute is filled")
|
||||
return false
|
||||
}
|
||||
|
||||
// Values should be between 0 and 1
|
||||
if (ofAvailableSpace !in 0f..1f || ofRemainderSpace !in 0f..1f) {
|
||||
Log.e(TAG, "SizeSpec#isValid - values should be between 0 and 1")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "SizeSpec(fixedSize=$fixedSize, ofAvailableSpace=$ofAvailableSpace, " +
|
||||
"ofRemainderSpace=$ofRemainderSpace)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
/* Copyright 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.
|
||||
*/
|
||||
-->
|
||||
|
||||
<!-- Attributes have to be copied to test for correct parsing of files -->
|
||||
<resources>
|
||||
<!-- Responsive grids attributes -->
|
||||
<declare-styleable name="WorkspaceSpec">
|
||||
<attr name="specType" format="integer">
|
||||
<enum name="height" value="0" />
|
||||
<enum name="width" value="1" />
|
||||
</attr>
|
||||
<attr name="maxAvailableSize" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="SpecSize">
|
||||
<attr name="fixedSize" format="dimension" />
|
||||
<attr name="ofAvailableSpace" format="float" />
|
||||
<attr name="ofRemainderSpace" format="float" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
|
||||
<workspaceSpec
|
||||
launcher:specType="height"
|
||||
launcher:maxAvailableSize="648dp">
|
||||
<!-- missing startPadding -->
|
||||
<endPadding
|
||||
launcher:ofAvailableSpace="0.05" />
|
||||
<gutter
|
||||
launcher:fixedSize="16dp" />
|
||||
<cellSize
|
||||
launcher:ofRemainderSpace="0.2" />
|
||||
</workspaceSpec>
|
||||
|
||||
<workspaceSpec
|
||||
launcher:specType="height"
|
||||
launcher:maxAvailableSize="9999dp">
|
||||
<startPadding
|
||||
launcher:ofAvailableSpace="0.0306" />
|
||||
<endPadding
|
||||
launcher:ofAvailableSpace="0.068" />
|
||||
<gutter
|
||||
launcher:fixedSize="16dp" />
|
||||
<cellSize
|
||||
launcher:ofRemainderSpace="0.2" />
|
||||
</workspaceSpec>
|
||||
|
||||
<!-- Width spec is always the same -->
|
||||
<workspaceSpec
|
||||
launcher:specType="width"
|
||||
launcher:maxAvailableSize="9999dp">
|
||||
<startPadding
|
||||
launcher:ofRemainderSpace="0.21436227" />
|
||||
<endPadding
|
||||
launcher:ofRemainderSpace="0.21436227" />
|
||||
<gutter
|
||||
launcher:ofRemainderSpace="0.11425509" />
|
||||
<cellSize
|
||||
launcher:fixedSize="120dp" />
|
||||
</workspaceSpec>
|
||||
</workspaceSpecs>
|
||||
@@ -0,0 +1,59 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
|
||||
<workspaceSpec
|
||||
launcher:specType="height"
|
||||
launcher:maxAvailableSize="648dp">
|
||||
<startPadding
|
||||
launcher:ofAvailableSpace="0.0125" />
|
||||
<endPadding
|
||||
launcher:ofAvailableSpace="0.05" />
|
||||
<!-- more than 1 value in one tag -->
|
||||
<gutter
|
||||
launcher:ofAvailableSpace="0.0125"
|
||||
launcher:fixedSize="16dp" />
|
||||
<cellSize
|
||||
launcher:ofRemainderSpace="0.2" />
|
||||
</workspaceSpec>
|
||||
|
||||
<workspaceSpec
|
||||
launcher:specType="height"
|
||||
launcher:maxAvailableSize="9999dp">
|
||||
<startPadding
|
||||
launcher:ofAvailableSpace="0.0306" />
|
||||
<endPadding
|
||||
launcher:ofAvailableSpace="0.068" />
|
||||
<gutter
|
||||
launcher:fixedSize="16dp" />
|
||||
<cellSize
|
||||
launcher:ofRemainderSpace="0.2" />
|
||||
</workspaceSpec>
|
||||
|
||||
<!-- Width spec is always the same -->
|
||||
<workspaceSpec
|
||||
launcher:specType="width"
|
||||
launcher:maxAvailableSize="9999dp">
|
||||
<startPadding
|
||||
launcher:ofRemainderSpace="0.21436227" />
|
||||
<endPadding
|
||||
launcher:ofRemainderSpace="0.21436227" />
|
||||
<gutter
|
||||
launcher:ofRemainderSpace="0.11425509" />
|
||||
<cellSize
|
||||
launcher:fixedSize="120dp" />
|
||||
</workspaceSpec>
|
||||
</workspaceSpecs>
|
||||
@@ -0,0 +1,58 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
|
||||
<workspaceSpec
|
||||
launcher:specType="height"
|
||||
launcher:maxAvailableSize="648dp">
|
||||
<startPadding
|
||||
launcher:ofAvailableSpace="0.0125" />
|
||||
<endPadding
|
||||
launcher:ofAvailableSpace="0.05" />
|
||||
<gutter
|
||||
launcher:fixedSize="16dp" />
|
||||
<!-- value bigger than 1 -->
|
||||
<cellSize
|
||||
launcher:ofRemainderSpace="1.001" />
|
||||
</workspaceSpec>
|
||||
|
||||
<workspaceSpec
|
||||
launcher:specType="height"
|
||||
launcher:maxAvailableSize="9999dp">
|
||||
<startPadding
|
||||
launcher:ofAvailableSpace="0.0306" />
|
||||
<endPadding
|
||||
launcher:ofAvailableSpace="0.068" />
|
||||
<gutter
|
||||
launcher:fixedSize="16dp" />
|
||||
<cellSize
|
||||
launcher:ofRemainderSpace="0.2" />
|
||||
</workspaceSpec>
|
||||
|
||||
<!-- Width spec is always the same -->
|
||||
<workspaceSpec
|
||||
launcher:specType="width"
|
||||
launcher:maxAvailableSize="9999dp">
|
||||
<startPadding
|
||||
launcher:ofRemainderSpace="0.21436227" />
|
||||
<endPadding
|
||||
launcher:ofRemainderSpace="0.21436227" />
|
||||
<gutter
|
||||
launcher:ofRemainderSpace="0.11425509" />
|
||||
<cellSize
|
||||
launcher:fixedSize="120dp" />
|
||||
</workspaceSpec>
|
||||
</workspaceSpecs>
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
|
||||
<workspaceSpec
|
||||
launcher:specType="height"
|
||||
launcher:maxAvailableSize="648dp">
|
||||
<startPadding
|
||||
launcher:ofAvailableSpace="0.0125" />
|
||||
<endPadding
|
||||
launcher:ofAvailableSpace="0.05" />
|
||||
<gutter
|
||||
launcher:fixedSize="16dp" />
|
||||
<cellSize
|
||||
launcher:ofRemainderSpace="0.2" />
|
||||
</workspaceSpec>
|
||||
|
||||
<workspaceSpec
|
||||
launcher:specType="height"
|
||||
launcher:maxAvailableSize="9999dp">
|
||||
<startPadding
|
||||
launcher:ofAvailableSpace="0.0306" />
|
||||
<endPadding
|
||||
launcher:ofAvailableSpace="0.068" />
|
||||
<gutter
|
||||
launcher:fixedSize="16dp" />
|
||||
<cellSize
|
||||
launcher:ofRemainderSpace="0.2" />
|
||||
</workspaceSpec>
|
||||
|
||||
<!-- Width spec is always the same -->
|
||||
<workspaceSpec
|
||||
launcher:specType="width"
|
||||
launcher:maxAvailableSize="9999dp">
|
||||
<startPadding
|
||||
launcher:ofRemainderSpace="0.21436227" />
|
||||
<endPadding
|
||||
launcher:ofRemainderSpace="0.21436227" />
|
||||
<gutter
|
||||
launcher:ofRemainderSpace="0.11425509" />
|
||||
<cellSize
|
||||
launcher:fixedSize="120dp" />
|
||||
</workspaceSpec>
|
||||
</workspaceSpecs>
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.Surface
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.android.launcher3.util.DisplayController
|
||||
import com.android.launcher3.util.NavigationMode
|
||||
import com.android.launcher3.util.WindowBounds
|
||||
import com.android.launcher3.util.window.CachedDisplayInfo
|
||||
import com.android.launcher3.util.window.WindowManagerProxy
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
/**
|
||||
* This is an abstract class for DeviceProfile tests that create an InvariantDeviceProfile based on
|
||||
* a real device spec.
|
||||
*
|
||||
* For an implementation that mocks InvariantDeviceProfile, use [FakeInvariantDeviceProfileTest]
|
||||
*/
|
||||
abstract class AbstractDeviceProfileTest {
|
||||
protected var context: Context? = null
|
||||
protected open val runningContext: Context = ApplicationProvider.getApplicationContext()
|
||||
private var displayController: DisplayController = mock(DisplayController::class.java)
|
||||
private var windowManagerProxy: WindowManagerProxy = mock(WindowManagerProxy::class.java)
|
||||
private lateinit var originalDisplayController: DisplayController
|
||||
private lateinit var originalWindowManagerProxy: WindowManagerProxy
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val appContext: Context = ApplicationProvider.getApplicationContext()
|
||||
originalWindowManagerProxy = WindowManagerProxy.INSTANCE.get(appContext)
|
||||
originalDisplayController = DisplayController.INSTANCE.get(appContext)
|
||||
WindowManagerProxy.INSTANCE.initializeForTesting(windowManagerProxy)
|
||||
DisplayController.INSTANCE.initializeForTesting(displayController)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
WindowManagerProxy.INSTANCE.initializeForTesting(originalWindowManagerProxy)
|
||||
DisplayController.INSTANCE.initializeForTesting(originalDisplayController)
|
||||
}
|
||||
|
||||
class DeviceSpec(
|
||||
val naturalSize: Pair<Int, Int>,
|
||||
val densityDpi: Int,
|
||||
val statusBarNaturalPx: Int,
|
||||
val statusBarRotatedPx: Int,
|
||||
val gesturePx: Int,
|
||||
val cutoutPx: Int
|
||||
)
|
||||
|
||||
open val deviceSpecs =
|
||||
mapOf(
|
||||
"phone" to
|
||||
DeviceSpec(
|
||||
Pair(1080, 2400),
|
||||
densityDpi = 420,
|
||||
statusBarNaturalPx = 118,
|
||||
statusBarRotatedPx = 74,
|
||||
gesturePx = 63,
|
||||
cutoutPx = 118
|
||||
),
|
||||
"tablet" to
|
||||
DeviceSpec(
|
||||
Pair(2560, 1600),
|
||||
densityDpi = 320,
|
||||
statusBarNaturalPx = 104,
|
||||
statusBarRotatedPx = 104,
|
||||
gesturePx = 0,
|
||||
cutoutPx = 0
|
||||
),
|
||||
)
|
||||
|
||||
protected fun initializeVarsForPhone(
|
||||
deviceSpec: DeviceSpec,
|
||||
isGestureMode: Boolean = true,
|
||||
isVerticalBar: Boolean = false
|
||||
) {
|
||||
val (naturalX, naturalY) = deviceSpec.naturalSize
|
||||
val windowsBounds = phoneWindowsBounds(deviceSpec, isGestureMode, naturalX, naturalY)
|
||||
val displayInfo =
|
||||
CachedDisplayInfo(Point(naturalX, naturalY), Surface.ROTATION_0, Rect(0, 0, 0, 0))
|
||||
val perDisplayBoundsCache = mapOf(displayInfo to windowsBounds)
|
||||
|
||||
initializeCommonVars(
|
||||
perDisplayBoundsCache,
|
||||
displayInfo,
|
||||
rotation = if (isVerticalBar) Surface.ROTATION_90 else Surface.ROTATION_0,
|
||||
isGestureMode,
|
||||
densityDpi = deviceSpec.densityDpi
|
||||
)
|
||||
}
|
||||
|
||||
protected fun initializeVarsForTablet(
|
||||
deviceSpec: DeviceSpec,
|
||||
isLandscape: Boolean = false,
|
||||
isGestureMode: Boolean = true
|
||||
) {
|
||||
val (naturalX, naturalY) = deviceSpec.naturalSize
|
||||
val windowsBounds = tabletWindowsBounds(deviceSpec, naturalX, naturalY)
|
||||
val displayInfo =
|
||||
CachedDisplayInfo(Point(naturalX, naturalY), Surface.ROTATION_0, Rect(0, 0, 0, 0))
|
||||
val perDisplayBoundsCache = mapOf(displayInfo to windowsBounds)
|
||||
|
||||
initializeCommonVars(
|
||||
perDisplayBoundsCache,
|
||||
displayInfo,
|
||||
rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90,
|
||||
isGestureMode,
|
||||
densityDpi = deviceSpec.densityDpi
|
||||
)
|
||||
}
|
||||
|
||||
protected fun initializeVarsForTwoPanel(
|
||||
deviceTabletSpec: DeviceSpec,
|
||||
deviceSpec: DeviceSpec,
|
||||
isLandscape: Boolean = false,
|
||||
isGestureMode: Boolean = true
|
||||
) {
|
||||
val (tabletNaturalX, tabletNaturalY) = deviceTabletSpec.naturalSize
|
||||
val tabletWindowsBounds =
|
||||
tabletWindowsBounds(deviceTabletSpec, tabletNaturalX, tabletNaturalY)
|
||||
val tabletDisplayInfo =
|
||||
CachedDisplayInfo(
|
||||
Point(tabletNaturalX, tabletNaturalY),
|
||||
Surface.ROTATION_0,
|
||||
Rect(0, 0, 0, 0)
|
||||
)
|
||||
|
||||
val (phoneNaturalX, phoneNaturalY) = deviceSpec.naturalSize
|
||||
val phoneWindowsBounds =
|
||||
phoneWindowsBounds(deviceSpec, isGestureMode, phoneNaturalX, phoneNaturalY)
|
||||
val phoneDisplayInfo =
|
||||
CachedDisplayInfo(
|
||||
Point(phoneNaturalX, phoneNaturalY),
|
||||
Surface.ROTATION_0,
|
||||
Rect(0, 0, 0, 0)
|
||||
)
|
||||
|
||||
val perDisplayBoundsCache =
|
||||
mapOf(tabletDisplayInfo to tabletWindowsBounds, phoneDisplayInfo to phoneWindowsBounds)
|
||||
|
||||
initializeCommonVars(
|
||||
perDisplayBoundsCache,
|
||||
tabletDisplayInfo,
|
||||
rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90,
|
||||
isGestureMode,
|
||||
densityDpi = deviceTabletSpec.densityDpi
|
||||
)
|
||||
}
|
||||
|
||||
private fun phoneWindowsBounds(
|
||||
deviceSpec: DeviceSpec,
|
||||
isGestureMode: Boolean,
|
||||
naturalX: Int,
|
||||
naturalY: Int
|
||||
): Array<WindowBounds> {
|
||||
val buttonsNavHeight = Utilities.dpToPx(48f, deviceSpec.densityDpi)
|
||||
|
||||
val rotation0Insets =
|
||||
Rect(
|
||||
0,
|
||||
max(deviceSpec.statusBarNaturalPx, deviceSpec.cutoutPx),
|
||||
0,
|
||||
if (isGestureMode) deviceSpec.gesturePx else buttonsNavHeight
|
||||
)
|
||||
val rotation90Insets =
|
||||
Rect(
|
||||
deviceSpec.cutoutPx,
|
||||
deviceSpec.statusBarRotatedPx,
|
||||
if (isGestureMode) 0 else buttonsNavHeight,
|
||||
if (isGestureMode) deviceSpec.gesturePx else 0
|
||||
)
|
||||
val rotation180Insets =
|
||||
Rect(
|
||||
0,
|
||||
deviceSpec.statusBarNaturalPx,
|
||||
0,
|
||||
max(
|
||||
if (isGestureMode) deviceSpec.gesturePx else buttonsNavHeight,
|
||||
deviceSpec.cutoutPx
|
||||
)
|
||||
)
|
||||
val rotation270Insets =
|
||||
Rect(
|
||||
if (isGestureMode) 0 else buttonsNavHeight,
|
||||
deviceSpec.statusBarRotatedPx,
|
||||
deviceSpec.cutoutPx,
|
||||
if (isGestureMode) deviceSpec.gesturePx else 0
|
||||
)
|
||||
|
||||
return arrayOf(
|
||||
WindowBounds(Rect(0, 0, naturalX, naturalY), rotation0Insets, Surface.ROTATION_0),
|
||||
WindowBounds(Rect(0, 0, naturalY, naturalX), rotation90Insets, Surface.ROTATION_90),
|
||||
WindowBounds(Rect(0, 0, naturalX, naturalY), rotation180Insets, Surface.ROTATION_180),
|
||||
WindowBounds(Rect(0, 0, naturalY, naturalX), rotation270Insets, Surface.ROTATION_270)
|
||||
)
|
||||
}
|
||||
|
||||
private fun tabletWindowsBounds(
|
||||
deviceSpec: DeviceSpec,
|
||||
naturalX: Int,
|
||||
naturalY: Int
|
||||
): Array<WindowBounds> {
|
||||
val naturalInsets = Rect(0, deviceSpec.statusBarNaturalPx, 0, 0)
|
||||
val rotatedInsets = Rect(0, deviceSpec.statusBarRotatedPx, 0, 0)
|
||||
|
||||
return arrayOf(
|
||||
WindowBounds(Rect(0, 0, naturalX, naturalY), naturalInsets, Surface.ROTATION_0),
|
||||
WindowBounds(Rect(0, 0, naturalY, naturalX), rotatedInsets, Surface.ROTATION_90),
|
||||
WindowBounds(Rect(0, 0, naturalX, naturalY), naturalInsets, Surface.ROTATION_180),
|
||||
WindowBounds(Rect(0, 0, naturalY, naturalX), rotatedInsets, Surface.ROTATION_270)
|
||||
)
|
||||
}
|
||||
|
||||
private fun initializeCommonVars(
|
||||
perDisplayBoundsCache: Map<CachedDisplayInfo, Array<WindowBounds>>,
|
||||
displayInfo: CachedDisplayInfo,
|
||||
rotation: Int,
|
||||
isGestureMode: Boolean = true,
|
||||
densityDpi: Int
|
||||
) {
|
||||
val windowsBounds = perDisplayBoundsCache[displayInfo]!!
|
||||
val realBounds = windowsBounds[rotation]
|
||||
whenever(windowManagerProxy.getDisplayInfo(ArgumentMatchers.any())).thenReturn(displayInfo)
|
||||
whenever(windowManagerProxy.getRealBounds(ArgumentMatchers.any(), ArgumentMatchers.any()))
|
||||
.thenReturn(realBounds)
|
||||
whenever(windowManagerProxy.getRotation(ArgumentMatchers.any())).thenReturn(rotation)
|
||||
whenever(windowManagerProxy.getNavigationMode(ArgumentMatchers.any()))
|
||||
.thenReturn(
|
||||
if (isGestureMode) NavigationMode.NO_BUTTON else NavigationMode.THREE_BUTTONS
|
||||
)
|
||||
|
||||
val density = densityDpi / DisplayMetrics.DENSITY_DEFAULT.toFloat()
|
||||
val config =
|
||||
Configuration(runningContext.resources.configuration).apply {
|
||||
this.densityDpi = densityDpi
|
||||
screenWidthDp = (realBounds.bounds.width() / density).toInt()
|
||||
screenHeightDp = (realBounds.bounds.height() / density).toInt()
|
||||
smallestScreenWidthDp = min(screenWidthDp, screenHeightDp)
|
||||
}
|
||||
context = runningContext.createConfigurationContext(config)
|
||||
|
||||
val info = DisplayController.Info(context, windowManagerProxy, perDisplayBoundsCache)
|
||||
whenever(displayController.info).thenReturn(info)
|
||||
whenever(displayController.isTransientTaskbar).thenReturn(isGestureMode)
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -31,7 +31,13 @@ import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
abstract class DeviceProfileBaseTest {
|
||||
/**
|
||||
* This is an abstract class for DeviceProfile tests that don't need the real Context and mock an
|
||||
* InvariantDeviceProfile instead of creating one based on real values.
|
||||
*
|
||||
* For an implementation that creates InvariantDeviceProfile, use [AbstractDeviceProfileTest]
|
||||
*/
|
||||
abstract class FakeInvariantDeviceProfileTest {
|
||||
|
||||
protected var context: Context? = null
|
||||
protected var inv: InvariantDeviceProfile? = null
|
||||
@@ -18,7 +18,7 @@ package com.android.launcher3.nonquickstep
|
||||
import android.graphics.Rect
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.launcher3.DeviceProfileBaseTest
|
||||
import com.android.launcher3.FakeInvariantDeviceProfileTest
|
||||
import com.android.launcher3.util.WindowBounds
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
@@ -26,7 +26,7 @@ import org.junit.runner.RunWith
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class HotseatWidthCalculationTest : DeviceProfileBaseTest() {
|
||||
class HotseatWidthCalculationTest : FakeInvariantDeviceProfileTest() {
|
||||
|
||||
/**
|
||||
* This is a case when after setting the hotseat, the space needs to be recalculated but it
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.util.AttributeSet
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.tests.R as TestR
|
||||
import kotlin.IntArray
|
||||
|
||||
class TestResourceHelper(private val context: Context, private val specsFileId: Int) :
|
||||
ResourceHelper(context, specsFileId) {
|
||||
override fun obtainStyledAttributes(attrs: AttributeSet, styleId: IntArray): TypedArray {
|
||||
var clone = styleId.clone()
|
||||
if (styleId == R.styleable.SpecSize) clone = TestR.styleable.SpecSize
|
||||
else if (styleId == R.styleable.WorkspaceSpec) clone = TestR.styleable.WorkspaceSpec
|
||||
return context.obtainStyledAttributes(attrs, clone)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.workspace
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.android.launcher3.AbstractDeviceProfileTest
|
||||
import com.android.launcher3.tests.R as TestR
|
||||
import com.android.launcher3.util.TestResourceHelper
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class WorkspaceSpecsTest : AbstractDeviceProfileTest() {
|
||||
override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
initializeVarsForPhone(deviceSpecs["phone"]!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseValidFile() {
|
||||
val workspaceSpecs =
|
||||
WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
|
||||
assertThat(workspaceSpecs.workspaceHeightSpecList.size).isEqualTo(2)
|
||||
assertThat(workspaceSpecs.workspaceHeightSpecList[0].toString())
|
||||
.isEqualTo(
|
||||
"WorkspaceSpec(" +
|
||||
"maxAvailableSize=1701, " +
|
||||
"specType=HEIGHT, " +
|
||||
"startPadding=SizeSpec(fixedSize=0.0, " +
|
||||
"ofAvailableSpace=0.0125, " +
|
||||
"ofRemainderSpace=0.0), " +
|
||||
"endPadding=SizeSpec(fixedSize=0.0, " +
|
||||
"ofAvailableSpace=0.05, " +
|
||||
"ofRemainderSpace=0.0), " +
|
||||
"gutter=SizeSpec(fixedSize=42.0, " +
|
||||
"ofAvailableSpace=0.0, " +
|
||||
"ofRemainderSpace=0.0), " +
|
||||
"cellSize=SizeSpec(fixedSize=0.0, " +
|
||||
"ofAvailableSpace=0.0, " +
|
||||
"ofRemainderSpace=0.2)" +
|
||||
")"
|
||||
)
|
||||
assertThat(workspaceSpecs.workspaceHeightSpecList[1].toString())
|
||||
.isEqualTo(
|
||||
"WorkspaceSpec(" +
|
||||
"maxAvailableSize=26247, " +
|
||||
"specType=HEIGHT, " +
|
||||
"startPadding=SizeSpec(fixedSize=0.0, " +
|
||||
"ofAvailableSpace=0.0306, " +
|
||||
"ofRemainderSpace=0.0), " +
|
||||
"endPadding=SizeSpec(fixedSize=0.0, " +
|
||||
"ofAvailableSpace=0.068, " +
|
||||
"ofRemainderSpace=0.0), " +
|
||||
"gutter=SizeSpec(fixedSize=42.0, " +
|
||||
"ofAvailableSpace=0.0, " +
|
||||
"ofRemainderSpace=0.0), " +
|
||||
"cellSize=SizeSpec(fixedSize=0.0, " +
|
||||
"ofAvailableSpace=0.0, " +
|
||||
"ofRemainderSpace=0.2)" +
|
||||
")"
|
||||
)
|
||||
assertThat(workspaceSpecs.workspaceWidthSpecList.size).isEqualTo(1)
|
||||
assertThat(workspaceSpecs.workspaceWidthSpecList[0].toString())
|
||||
.isEqualTo(
|
||||
"WorkspaceSpec(" +
|
||||
"maxAvailableSize=26247, " +
|
||||
"specType=WIDTH, " +
|
||||
"startPadding=SizeSpec(fixedSize=0.0, " +
|
||||
"ofAvailableSpace=0.0, " +
|
||||
"ofRemainderSpace=0.21436226), " +
|
||||
"endPadding=SizeSpec(fixedSize=0.0, " +
|
||||
"ofAvailableSpace=0.0, " +
|
||||
"ofRemainderSpace=0.21436226), " +
|
||||
"gutter=SizeSpec(fixedSize=0.0, " +
|
||||
"ofAvailableSpace=0.0, " +
|
||||
"ofRemainderSpace=0.11425509), " +
|
||||
"cellSize=SizeSpec(fixedSize=315.0, " +
|
||||
"ofAvailableSpace=0.0, " +
|
||||
"ofRemainderSpace=0.0)" +
|
||||
")"
|
||||
)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun parseInvalidFile_missingTag_throwsError() {
|
||||
WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_1))
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
|
||||
WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_2))
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun parseInvalidFile_valueBiggerThan1_throwsError() {
|
||||
WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_3))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user