Merge "Handle onFlingFinished onRecentsAnimationStart" into udc-qpr-dev
diff --git a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
index 225b4cd..672440f 100644
--- a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
+++ b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
@@ -13,155 +13,163 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:theme="@style/GestureTutorialActivity"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingTop="@dimen/gesture_tutorial_menu_padding_top"
- android:paddingBottom="@dimen/gesture_tutorial_menu_padding_bottom"
- android:paddingHorizontal="@dimen/gesture_tutorial_menu_padding_horizontal"
- android:background="?androidprv:attr/materialColorSurfaceContainer">
+ android:background="?androidprv:attr/materialColorSurfaceContainer"
+ android:fitsSystemWindows="true">
<androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/gesture_tutorial_menu_home_button"
- android:layout_width="0dp"
- android:layout_height="@dimen/gesture_tutorial_menu_button_height"
- android:layout_marginEnd="@dimen/gesture_tutorial_menu_button_spacing"
- android:layout_marginBottom="24dp"
- android:background="@drawable/gesture_tutorial_menu_home_button_background"
- android:clipToOutline="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="@dimen/gesture_tutorial_menu_padding_top"
+ android:paddingBottom="@dimen/gesture_tutorial_menu_padding_bottom"
+ android:paddingHorizontal="@dimen/gesture_tutorial_menu_padding_horizontal"
+ android:clipToPadding="false">
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/guideline"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/gesture_tutorial_menu_back_button">
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/gesture_tutorial_home_step_shape"
- android:scaleType="fitXY"
- android:adjustViewBounds="true"
-
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- <TextView
- style="@style/TextAppearance.GestureTutorial.MenuButton.Home"
- android:id="@+id/gesture_tutorial_menu_home_button_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/home_gesture_tutorial_title"
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/gesture_tutorial_menu_home_button"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+ android:layout_marginEnd="@dimen/gesture_tutorial_menu_button_spacing"
+ android:layout_marginBottom="24dp"
+ android:background="@drawable/gesture_tutorial_menu_home_button_background"
+ android:clipToOutline="true"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/gesture_tutorial_menu_back_button">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gesture_tutorial_home_step_shape"
+ android:scaleType="fitXY"
+ android:adjustViewBounds="true"
+
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ style="@style/TextAppearance.GestureTutorial.MenuButton.Home"
+ android:id="@+id/gesture_tutorial_menu_home_button_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/home_gesture_tutorial_title"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/gesture_tutorial_menu_back_button"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+ android:layout_marginEnd="@dimen/gesture_tutorial_menu_button_spacing"
+ android:layout_marginBottom="24dp"
+ android:background="@drawable/gesture_tutorial_menu_back_button_background"
+ android:clipToOutline="true"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/guideline"
+ app:layout_constraintStart_toEndOf="@id/gesture_tutorial_menu_home_button"
+ app:layout_constraintEnd_toStartOf="@id/gesture_tutorial_menu_overview_button">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gesture_tutorial_back_step_shape"
+ android:layout_marginBottom="@dimen/gesture_tutorial_menu_back_shape_bottom_margin"
+ android:scaleType="fitXY"
+ android:adjustViewBounds="true"
+
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <TextView
+ style="@style/TextAppearance.GestureTutorial.MenuButton.Back"
+ android:id="@+id/gesture_tutorial_menu_back_button_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/back_gesture_tutorial_title"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/gesture_tutorial_menu_overview_button"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+ android:layout_marginBottom="24dp"
+ android:background="@drawable/gesture_tutorial_menu_overview_button_background"
+ android:clipToOutline="true"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/guideline"
+ app:layout_constraintStart_toEndOf="@id/gesture_tutorial_menu_back_button"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gesture_tutorial_overview_step_shape"
+ android:scaleType="fitXY"
+ android:adjustViewBounds="true"
+
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ style="@style/TextAppearance.GestureTutorial.MenuButton.Overview"
+ android:id="@+id/gesture_tutorial_menu_overview_button_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/overview_gesture_tutorial_title"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+
+ app:layout_constraintGuide_end="@dimen/gesture_tutorial_menu_done_button_spacing"/>
+
+ <Button
+ style="@style/TextAppearance.GestureTutorial.ButtonLabel"
+ android:id="@+id/gesture_tutorial_menu_done_button"
+ android:layout_width="wrap_content"
+ android:layout_height="40dp"
+ android:layout_marginVertical="16dp"
+ android:text="@string/gesture_tutorial_action_button_label"
+ android:background="@drawable/gesture_tutorial_action_button_background"
+ android:backgroundTint="?androidprv:attr/materialColorPrimary"
+ android:stateListAnimator="@null"
+
+ app:layout_constraintTop_toBottomOf="@id/guideline"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/gesture_tutorial_menu_back_button"
- android:layout_width="0dp"
- android:layout_height="@dimen/gesture_tutorial_menu_button_height"
- android:layout_marginEnd="@dimen/gesture_tutorial_menu_button_spacing"
- android:layout_marginBottom="24dp"
- android:background="@drawable/gesture_tutorial_menu_back_button_background"
- android:clipToOutline="true"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/guideline"
- app:layout_constraintStart_toEndOf="@id/gesture_tutorial_menu_home_button"
- app:layout_constraintEnd_toStartOf="@id/gesture_tutorial_menu_overview_button">
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/gesture_tutorial_back_step_shape"
- android:layout_marginBottom="@dimen/gesture_tutorial_menu_back_shape_bottom_margin"
- android:scaleType="fitXY"
- android:adjustViewBounds="true"
-
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"/>
-
- <TextView
- style="@style/TextAppearance.GestureTutorial.MenuButton.Back"
- android:id="@+id/gesture_tutorial_menu_back_button_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/back_gesture_tutorial_title"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- </androidx.constraintlayout.widget.ConstraintLayout>
-
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/gesture_tutorial_menu_overview_button"
- android:layout_width="0dp"
- android:layout_height="@dimen/gesture_tutorial_menu_button_height"
- android:layout_marginBottom="24dp"
- android:background="@drawable/gesture_tutorial_menu_overview_button_background"
- android:clipToOutline="true"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/guideline"
- app:layout_constraintStart_toEndOf="@id/gesture_tutorial_menu_back_button"
- app:layout_constraintEnd_toEndOf="parent">
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/gesture_tutorial_overview_step_shape"
- android:scaleType="fitXY"
- android:adjustViewBounds="true"
-
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- <TextView
- style="@style/TextAppearance.GestureTutorial.MenuButton.Overview"
- android:id="@+id/gesture_tutorial_menu_overview_button_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/overview_gesture_tutorial_title"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- </androidx.constraintlayout.widget.ConstraintLayout>
-
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/guideline"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
-
- app:layout_constraintGuide_end="@dimen/gesture_tutorial_menu_done_button_spacing"/>
-
- <Button
- style="@style/TextAppearance.GestureTutorial.ButtonLabel"
- android:id="@+id/gesture_tutorial_menu_done_button"
- android:layout_width="wrap_content"
- android:layout_height="40dp"
- android:layout_marginVertical="16dp"
- android:text="@string/gesture_tutorial_action_button_label"
- android:background="@drawable/gesture_tutorial_action_button_background"
- android:backgroundTint="?androidprv:attr/materialColorPrimary"
- android:stateListAnimator="@null"
-
- app:layout_constraintTop_toBottomOf="@id/guideline"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+</FrameLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/activity_allset.xml b/quickstep/res/layout/activity_allset.xml
index 2c312a7..685a151 100644
--- a/quickstep/res/layout/activity_allset.xml
+++ b/quickstep/res/layout/activity_allset.xml
@@ -41,6 +41,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
+ android:fitsSystemWindows="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
@@ -95,7 +96,6 @@
style="@style/TextAppearance.GestureTutorial.Feedback.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="24dp"
android:text="@string/allset_hint"
android:textSize="@dimen/allset_page_swipe_up_text_size"
android:gravity="center_horizontal"
diff --git a/quickstep/res/layout/gesture_tutorial_step_menu.xml b/quickstep/res/layout/gesture_tutorial_step_menu.xml
index cf78b1b..c8ee6e9 100644
--- a/quickstep/res/layout/gesture_tutorial_step_menu.xml
+++ b/quickstep/res/layout/gesture_tutorial_step_menu.xml
@@ -13,154 +13,161 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:theme="@style/GestureTutorialActivity"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingTop="@dimen/gesture_tutorial_menu_padding_top"
- android:paddingBottom="@dimen/gesture_tutorial_menu_padding_bottom"
- android:paddingHorizontal="@dimen/gesture_tutorial_menu_padding_horizontal"
android:background="?androidprv:attr/materialColorSurfaceContainer"
- android:clipToPadding="false">
+ android:fitsSystemWindows="true">
<androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/gesture_tutorial_menu_home_button"
android:layout_width="match_parent"
- android:layout_height="@dimen/gesture_tutorial_menu_button_height"
- android:background="@drawable/gesture_tutorial_menu_home_button_background"
- android:clipToOutline="true"
- app:layout_constraintVertical_chainStyle="packed"
+ android:layout_height="match_parent"
+ android:paddingTop="@dimen/gesture_tutorial_menu_padding_top"
+ android:paddingBottom="@dimen/gesture_tutorial_menu_padding_bottom"
+ android:paddingHorizontal="@dimen/gesture_tutorial_menu_padding_horizontal"
+ android:clipToPadding="false">
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/gesture_tutorial_menu_back_button"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent">
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/gesture_tutorial_home_step_shape"
- android:scaleType="fitXY"
- android:adjustViewBounds="true"
-
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- <TextView
- style="@style/TextAppearance.GestureTutorial.MenuButton.Home"
- android:id="@+id/gesture_tutorial_menu_home_button_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/home_gesture_tutorial_title"
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/gesture_tutorial_menu_home_button"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+ android:background="@drawable/gesture_tutorial_menu_home_button_background"
+ android:clipToOutline="true"
+ app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/gesture_tutorial_menu_back_button"
app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gesture_tutorial_home_step_shape"
+ android:scaleType="fitXY"
+ android:adjustViewBounds="true"
+
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ style="@style/TextAppearance.GestureTutorial.MenuButton.Home"
+ android:id="@+id/gesture_tutorial_menu_home_button_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/home_gesture_tutorial_title"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/gesture_tutorial_menu_back_button"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+ android:layout_marginTop="@dimen/gesture_tutorial_menu_button_spacing"
+ android:background="@drawable/gesture_tutorial_menu_back_button_background"
+ android:clipToOutline="true"
+
+ app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_menu_home_button"
+ app:layout_constraintBottom_toTopOf="@id/gesture_tutorial_menu_overview_button"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gesture_tutorial_back_step_shape"
+ android:scaleType="fitXY"
+ android:adjustViewBounds="true"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <TextView
+ style="@style/TextAppearance.GestureTutorial.MenuButton.Back"
+ android:id="@+id/gesture_tutorial_menu_back_button_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/back_gesture_tutorial_title"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/gesture_tutorial_menu_overview_button"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+ android:layout_marginTop="@dimen/gesture_tutorial_menu_button_spacing"
+ android:background="@drawable/gesture_tutorial_menu_overview_button_background"
+ android:clipToOutline="true"
+
+ app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_menu_back_button"
+ app:layout_constraintBottom_toTopOf="@id/guideline"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gesture_tutorial_overview_step_shape"
+ android:scaleType="fitXY"
+ android:adjustViewBounds="true"
+
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ style="@style/TextAppearance.GestureTutorial.MenuButton.Overview"
+ android:id="@+id/gesture_tutorial_menu_overview_button_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/overview_gesture_tutorial_title"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+
+ app:layout_constraintGuide_end="@dimen/gesture_tutorial_menu_done_button_spacing"/>
+
+ <Button
+ style="@style/TextAppearance.GestureTutorial.ButtonLabel"
+ android:id="@+id/gesture_tutorial_menu_done_button"
+ android:layout_width="wrap_content"
+ android:layout_height="40dp"
+ android:layout_marginVertical="16dp"
+ android:text="@string/gesture_tutorial_action_button_label"
+ android:background="@drawable/gesture_tutorial_action_button_background"
+ android:backgroundTint="?androidprv:attr/materialColorPrimary"
+ android:stateListAnimator="@null"
+
+ app:layout_constraintTop_toBottomOf="@id/guideline"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/gesture_tutorial_menu_back_button"
- android:layout_width="match_parent"
- android:layout_height="@dimen/gesture_tutorial_menu_button_height"
- android:layout_marginTop="@dimen/gesture_tutorial_menu_button_spacing"
- android:background="@drawable/gesture_tutorial_menu_back_button_background"
- android:clipToOutline="true"
-
- app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_menu_home_button"
- app:layout_constraintBottom_toTopOf="@id/gesture_tutorial_menu_overview_button"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent">
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/gesture_tutorial_back_step_shape"
- android:scaleType="fitXY"
- android:adjustViewBounds="true"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"/>
-
- <TextView
- style="@style/TextAppearance.GestureTutorial.MenuButton.Back"
- android:id="@+id/gesture_tutorial_menu_back_button_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/back_gesture_tutorial_title"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- </androidx.constraintlayout.widget.ConstraintLayout>
-
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/gesture_tutorial_menu_overview_button"
- android:layout_width="match_parent"
- android:layout_height="@dimen/gesture_tutorial_menu_button_height"
- android:layout_marginTop="@dimen/gesture_tutorial_menu_button_spacing"
- android:background="@drawable/gesture_tutorial_menu_overview_button_background"
- android:clipToOutline="true"
-
- app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_menu_back_button"
- app:layout_constraintBottom_toTopOf="@id/guideline"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent">
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/gesture_tutorial_overview_step_shape"
- android:scaleType="fitXY"
- android:adjustViewBounds="true"
-
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- <TextView
- style="@style/TextAppearance.GestureTutorial.MenuButton.Overview"
- android:id="@+id/gesture_tutorial_menu_overview_button_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/overview_gesture_tutorial_title"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- </androidx.constraintlayout.widget.ConstraintLayout>
-
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/guideline"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
-
- app:layout_constraintGuide_end="@dimen/gesture_tutorial_menu_done_button_spacing"/>
-
- <Button
- style="@style/TextAppearance.GestureTutorial.ButtonLabel"
- android:id="@+id/gesture_tutorial_menu_done_button"
- android:layout_width="wrap_content"
- android:layout_height="40dp"
- android:layout_marginVertical="16dp"
- android:text="@string/gesture_tutorial_action_button_label"
- android:background="@drawable/gesture_tutorial_action_button_background"
- android:backgroundTint="?androidprv:attr/materialColorPrimary"
- android:stateListAnimator="@null"
-
- app:layout_constraintTop_toBottomOf="@id/guideline"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+</FrameLayout>
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/interaction/MenuFragment.java b/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
index 46f79b1..c19d44a 100644
--- a/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
@@ -19,7 +19,6 @@
import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_TUTORIAL_TYPE;
import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_USE_TUTORIAL_MENU;
-import android.graphics.Rect;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -28,33 +27,17 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
/** Displays the gesture nav tutorial menu. */
public final class MenuFragment extends GestureSandboxFragment {
- @NonNull private Rect mInsets = new Rect();
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mInsets = InvariantDeviceProfile.INSTANCE.get(getContext())
- .getDeviceProfile(getContext()).getInsets();
- }
-
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
- View root = inflater.inflate(
+ final View root = inflater.inflate(
R.layout.gesture_tutorial_step_menu, container, false);
- root.setPadding(
- root.getPaddingLeft() + mInsets.left,
- root.getPaddingTop() + mInsets.top,
- root.getPaddingRight() + mInsets.right,
- root.getPaddingBottom() + mInsets.bottom);
-
root.findViewById(R.id.gesture_tutorial_menu_home_button).setOnClickListener(
v -> launchTutorialStep(TutorialController.TutorialType.HOME_NAVIGATION));
root.findViewById(R.id.gesture_tutorial_menu_back_button).setOnClickListener(
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index bd47fca..463a243 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -164,7 +164,7 @@
public int iconSizePx;
public int iconTextSizePx;
public int iconDrawablePaddingPx;
- public int iconDrawablePaddingOriginalPx;
+ private final int mIconDrawablePaddingOriginalPx;
public boolean iconCenterVertically;
public float cellScaleToFit;
@@ -456,7 +456,7 @@
cellStyle = context.obtainStyledAttributes(R.style.CellStyleDefault,
R.styleable.CellStyle);
}
- iconDrawablePaddingOriginalPx = cellStyle.getDimensionPixelSize(
+ mIconDrawablePaddingOriginalPx = cellStyle.getDimensionPixelSize(
R.styleable.CellStyle_iconDrawablePadding, 0);
cellStyle.recycle();
@@ -883,7 +883,6 @@
iconCenterVertically = mIsScalableGrid || mIsResponsiveGrid;
updateIconSize(1f, res);
-
updateWorkspacePadding();
// Check to see if the icons fit within the available height.
@@ -925,6 +924,25 @@
+ cellLayoutPaddingPx.left + cellLayoutPaddingPx.right;
}
+ private int getNormalizedIconDrawablePadding() {
+ // TODO(b/235886078): workaround needed because of this bug
+ // Icons are 10% larger on XML than their visual size,
+ // so remove that extra space to get labels closer to the correct padding
+ int iconVisibleSizePx = (int) Math.round(ICON_VISIBLE_AREA_FACTOR * iconSizePx);
+ return Math.max(0, mIconDrawablePaddingOriginalPx - ((iconSizePx - iconVisibleSizePx) / 2));
+ }
+
+ private int getNormalizedFolderChildDrawablePaddingPx(int textHeight) {
+ // TODO(b/235886078): workaround needed because of this bug
+ // Icons are 10% larger on XML than their visual size,
+ // so remove that extra space to get labels closer to the correct padding
+ int drawablePadding = (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3;
+
+ int iconVisibleSizePx = Math.round(ICON_VISIBLE_AREA_FACTOR * folderChildIconSizePx);
+ int iconSizeDiff = folderChildIconSizePx - iconVisibleSizePx;
+ return Math.max(0, drawablePadding - iconSizeDiff / 2);
+ }
+
/**
* Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx,
* iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
@@ -937,43 +955,36 @@
// Workspace
final boolean isVerticalLayout = isVerticalBarLayout();
- iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * iconScale);
cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv, scale);
- int cellTextAndPaddingHeight =
- iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx);
if (mIsResponsiveGrid) {
- int cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
-
cellWidthPx = mResponsiveWidthSpec.getCellSizePx();
cellHeightPx = mResponsiveHeightSpec.getCellSizePx();
if (cellWidthPx < iconSizePx) {
// get a smaller icon size
iconSizePx = mIconSizeSteps.getIconSmallerThan(cellWidthPx);
- // calculate new cellContentHeight
- cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
}
+ iconDrawablePaddingPx = getNormalizedIconDrawablePadding();
+ int iconTextHeight = Utilities.calculateTextHeight(iconTextSizePx);
+ int cellContentHeight = iconSizePx + iconDrawablePaddingPx + iconTextHeight;
+
while (iconSizePx > mIconSizeSteps.minimumIconSize()
&& cellContentHeight > cellHeightPx) {
- int extraHeightRequired = cellContentHeight - cellHeightPx;
- int newPadding = iconDrawablePaddingPx - extraHeightRequired;
- if (newPadding >= 0) {
- // Responsive uses the padding without scaling
- iconDrawablePaddingPx = iconDrawablePaddingOriginalPx = newPadding;
- cellTextAndPaddingHeight =
- iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx);
- } else {
+ iconDrawablePaddingPx -= cellContentHeight - cellHeightPx;
+ if (iconDrawablePaddingPx < 0) {
// get a smaller icon size
iconSizePx = mIconSizeSteps.getNextLowerIconSize(iconSizePx);
+ iconDrawablePaddingPx = getNormalizedIconDrawablePadding();
}
// calculate new cellContentHeight
- cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
+ cellContentHeight = iconSizePx + iconDrawablePaddingPx + iconTextHeight;
}
cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
} else if (mIsScalableGrid) {
+ iconDrawablePaddingPx = (int) (getNormalizedIconDrawablePadding() * iconScale);
cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale);
cellHeightPx = pxFromDp(inv.minCellSize[mTypeIndex].y, mMetrics, scale);
@@ -995,6 +1006,8 @@
}
}
+ int cellTextAndPaddingHeight =
+ iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx);
int cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
if (cellHeightPx < cellContentHeight) {
// If cellHeight no longer fit iconSize, reduce borderSpace to make cellHeight
@@ -1030,6 +1043,7 @@
desiredWorkspaceHorizontalMarginPx =
(int) (desiredWorkspaceHorizontalMarginOriginalPx * scale);
} else {
+ iconDrawablePaddingPx = (int) (getNormalizedIconDrawablePadding() * iconScale);
cellWidthPx = iconSizePx + iconDrawablePaddingPx;
cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR)
+ iconDrawablePaddingPx
@@ -1103,7 +1117,7 @@
if (mIsScalableGrid) {
allAppsIconSizePx = pxFromDp(inv.allAppsIconSize[mTypeIndex], mMetrics);
allAppsIconTextSizePx = pxFromSp(inv.allAppsIconTextSize[mTypeIndex], mMetrics);
- allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
+ allAppsIconDrawablePaddingPx = getNormalizedIconDrawablePadding();
allAppsCellWidthPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].x, mMetrics, scale);
if (allAppsCellWidthPx < allAppsIconSizePx) {
@@ -1145,7 +1159,7 @@
private void updateAllAppsWithResponsiveMeasures() {
allAppsIconSizePx = iconSizePx;
allAppsIconTextSizePx = iconTextSizePx;
- allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
+ allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
allAppsBorderSpacePx = new Point(
mAllAppsResponsiveWidthSpec.getGutterPx(),
@@ -1279,8 +1293,7 @@
}
- folderChildDrawablePaddingPx = Math.max(0,
- (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
+ folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight);
}
public void updateInsets(Rect insets) {
diff --git a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
index 0a95771..270672f 100644
--- a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
@@ -64,8 +64,8 @@
"\tinv.numColumns: 5\n" +
"\tinv.numSearchContainerColumns: 5\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 165.0px (62.857143dp)\n" +
- "\tcellHeightPx: 235.0px (89.52381dp)\n" +
+ "\tcellWidthPx: 159.0px (60.57143dp)\n" +
+ "\tcellHeightPx: 229.0px (87.2381dp)\n" +
"\tgetCellSize().x: 207.0px (78.85714dp)\n" +
"\tgetCellSize().y: 379.0px (144.38095dp)\n" +
"\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -76,14 +76,14 @@
"\tcellLayoutPaddingPx.bottom: 28.0px (10.666667dp)\n" +
"\ticonSizePx: 147.0px (56.0dp)\n" +
"\ticonTextSizePx: 38.0px (14.476191dp)\n" +
- "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+ "\ticonDrawablePaddingPx: 12.0px (4.571429dp)\n" +
"\tinv.numFolderRows: 4\n" +
"\tinv.numFolderColumns: 4\n" +
"\tfolderCellWidthPx: 195.0px (74.28571dp)\n" +
"\tfolderCellHeightPx: 230.0px (87.61905dp)\n" +
"\tfolderChildIconSizePx: 147.0px (56.0dp)\n" +
"\tfolderChildTextSizePx: 38.0px (14.476191dp)\n" +
- "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 4.0px (1.5238096dp)\n" +
"\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -139,8 +139,8 @@
"\tworkspacePadding.bottom: 203.0px (77.333336dp)\n" +
"\ticonScale: 1.0px (0.3809524dp)\n" +
"\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
- "\textraSpace: 722.0px (275.0476dp)\n" +
- "\tunscaled extraSpace: 722.0px (275.0476dp)\n" +
+ "\textraSpace: 752.0px (286.4762dp)\n" +
+ "\tunscaled extraSpace: 752.0px (286.4762dp)\n" +
"\tmaxEmptySpace: 0.0px (0.0dp)\n" +
"\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
"\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -201,8 +201,8 @@
"\tinv.numColumns: 5\n" +
"\tinv.numSearchContainerColumns: 5\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 165.0px (62.857143dp)\n" +
- "\tcellHeightPx: 235.0px (89.52381dp)\n" +
+ "\tcellWidthPx: 159.0px (60.57143dp)\n" +
+ "\tcellHeightPx: 229.0px (87.2381dp)\n" +
"\tgetCellSize().x: 207.0px (78.85714dp)\n" +
"\tgetCellSize().y: 383.0px (145.90475dp)\n" +
"\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -213,14 +213,14 @@
"\tcellLayoutPaddingPx.bottom: 28.0px (10.666667dp)\n" +
"\ticonSizePx: 147.0px (56.0dp)\n" +
"\ticonTextSizePx: 38.0px (14.476191dp)\n" +
- "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+ "\ticonDrawablePaddingPx: 12.0px (4.571429dp)\n" +
"\tinv.numFolderRows: 4\n" +
"\tinv.numFolderColumns: 4\n" +
"\tfolderCellWidthPx: 195.0px (74.28571dp)\n" +
"\tfolderCellHeightPx: 230.0px (87.61905dp)\n" +
"\tfolderChildIconSizePx: 147.0px (56.0dp)\n" +
"\tfolderChildTextSizePx: 38.0px (14.476191dp)\n" +
- "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 4.0px (1.5238096dp)\n" +
"\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -276,8 +276,8 @@
"\tworkspacePadding.bottom: 245.0px (93.333336dp)\n" +
"\ticonScale: 1.0px (0.3809524dp)\n" +
"\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
- "\textraSpace: 743.0px (283.0476dp)\n" +
- "\tunscaled extraSpace: 743.0px (283.0476dp)\n" +
+ "\textraSpace: 773.0px (294.4762dp)\n" +
+ "\tunscaled extraSpace: 773.0px (294.4762dp)\n" +
"\tmaxEmptySpace: 0.0px (0.0dp)\n" +
"\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
"\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -338,7 +338,7 @@
"\tinv.numColumns: 5\n" +
"\tinv.numSearchContainerColumns: 5\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 158.0px (60.190475dp)\n" +
+ "\tcellWidthPx: 152.0px (57.904762dp)\n" +
"\tcellHeightPx: 166.0px (63.238094dp)\n" +
"\tgetCellSize().x: 368.0px (140.19048dp)\n" +
"\tgetCellSize().y: 193.0px (73.52381dp)\n" +
@@ -357,7 +357,7 @@
"\tfolderCellHeightPx: 205.0px (78.09524dp)\n" +
"\tfolderChildIconSizePx: 131.0px (49.904762dp)\n" +
"\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
- "\tfolderChildDrawablePaddingPx: 9.0px (3.4285715dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 4.0px (1.5238096dp)\n" +
"\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -475,7 +475,7 @@
"\tinv.numColumns: 5\n" +
"\tinv.numSearchContainerColumns: 5\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 158.0px (60.190475dp)\n" +
+ "\tcellWidthPx: 152.0px (57.904762dp)\n" +
"\tcellHeightPx: 166.0px (63.238094dp)\n" +
"\tgetCellSize().x: 393.0px (149.71428dp)\n" +
"\tgetCellSize().y: 180.0px (68.57143dp)\n" +
@@ -494,7 +494,7 @@
"\tfolderCellHeightPx: 192.0px (73.14286dp)\n" +
"\tfolderChildIconSizePx: 123.0px (46.857143dp)\n" +
"\tfolderChildTextSizePx: 32.0px (12.190476dp)\n" +
- "\tfolderChildDrawablePaddingPx: 8.0px (3.047619dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 3.0px (1.1428572dp)\n" +
"\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -625,14 +625,14 @@
"\tcellLayoutPaddingPx.bottom: 59.0px (29.5dp)\n" +
"\ticonSizePx: 120.0px (60.0dp)\n" +
"\ticonTextSizePx: 28.0px (14.0dp)\n" +
- "\ticonDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\ticonDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 3\n" +
"\tfolderCellWidthPx: 240.0px (120.0dp)\n" +
"\tfolderCellHeightPx: 208.0px (104.0dp)\n" +
"\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
"\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
- "\tfolderChildDrawablePaddingPx: 16.0px (8.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 11.0px (5.5dp)\n" +
"\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
@@ -649,7 +649,7 @@
"\tallAppsCloseDuration: 500\n" +
"\tallAppsIconSizePx: 120.0px (60.0dp)\n" +
"\tallAppsIconTextSizePx: 28.0px (14.0dp)\n" +
- "\tallAppsIconDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\tallAppsIconDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tallAppsCellHeightPx: 284.0px (142.0dp)\n" +
"\tallAppsCellWidthPx: 252.0px (126.0dp)\n" +
"\tallAppsBorderSpacePxX: 32.0px (16.0dp)\n" +
@@ -763,14 +763,14 @@
"\tcellLayoutPaddingPx.bottom: 59.0px (29.5dp)\n" +
"\ticonSizePx: 120.0px (60.0dp)\n" +
"\ticonTextSizePx: 28.0px (14.0dp)\n" +
- "\ticonDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\ticonDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 3\n" +
"\tfolderCellWidthPx: 240.0px (120.0dp)\n" +
"\tfolderCellHeightPx: 208.0px (104.0dp)\n" +
"\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
"\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
- "\tfolderChildDrawablePaddingPx: 16.0px (8.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 11.0px (5.5dp)\n" +
"\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
@@ -787,7 +787,7 @@
"\tallAppsCloseDuration: 500\n" +
"\tallAppsIconSizePx: 120.0px (60.0dp)\n" +
"\tallAppsIconTextSizePx: 28.0px (14.0dp)\n" +
- "\tallAppsIconDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\tallAppsIconDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tallAppsCellHeightPx: 284.0px (142.0dp)\n" +
"\tallAppsCellWidthPx: 252.0px (126.0dp)\n" +
"\tallAppsBorderSpacePxX: 32.0px (16.0dp)\n" +
@@ -901,14 +901,14 @@
"\tcellLayoutPaddingPx.bottom: 72.0px (36.0dp)\n" +
"\ticonSizePx: 120.0px (60.0dp)\n" +
"\ticonTextSizePx: 28.0px (14.0dp)\n" +
- "\ticonDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\ticonDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 3\n" +
"\tfolderCellWidthPx: 204.0px (102.0dp)\n" +
"\tfolderCellHeightPx: 240.0px (120.0dp)\n" +
"\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
"\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
- "\tfolderChildDrawablePaddingPx: 27.0px (13.5dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 22.0px (11.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
@@ -925,7 +925,7 @@
"\tallAppsCloseDuration: 500\n" +
"\tallAppsIconSizePx: 120.0px (60.0dp)\n" +
"\tallAppsIconTextSizePx: 28.0px (14.0dp)\n" +
- "\tallAppsIconDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\tallAppsIconDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tallAppsCellHeightPx: 316.0px (158.0dp)\n" +
"\tallAppsCellWidthPx: 192.0px (96.0dp)\n" +
"\tallAppsBorderSpacePxX: 16.0px (8.0dp)\n" +
@@ -1039,14 +1039,14 @@
"\tcellLayoutPaddingPx.bottom: 72.0px (36.0dp)\n" +
"\ticonSizePx: 120.0px (60.0dp)\n" +
"\ticonTextSizePx: 28.0px (14.0dp)\n" +
- "\ticonDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\ticonDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 3\n" +
"\tfolderCellWidthPx: 204.0px (102.0dp)\n" +
"\tfolderCellHeightPx: 240.0px (120.0dp)\n" +
"\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
"\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
- "\tfolderChildDrawablePaddingPx: 27.0px (13.5dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 22.0px (11.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
@@ -1063,7 +1063,7 @@
"\tallAppsCloseDuration: 500\n" +
"\tallAppsIconSizePx: 120.0px (60.0dp)\n" +
"\tallAppsIconTextSizePx: 28.0px (14.0dp)\n" +
- "\tallAppsIconDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\tallAppsIconDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tallAppsCellHeightPx: 316.0px (158.0dp)\n" +
"\tallAppsCellWidthPx: 192.0px (96.0dp)\n" +
"\tallAppsBorderSpacePxX: 16.0px (8.0dp)\n" +
@@ -1170,8 +1170,8 @@
"\tinv.numColumns: 4\n" +
"\tinv.numSearchContainerColumns: 4\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 159.0px (60.57143dp)\n" +
- "\tcellHeightPx: 223.0px (84.95238dp)\n" +
+ "\tcellWidthPx: 154.0px (58.666668dp)\n" +
+ "\tcellHeightPx: 218.0px (83.04762dp)\n" +
"\tgetCellSize().x: 270.0px (102.85714dp)\n" +
"\tgetCellSize().y: 342.0px (130.28572dp)\n" +
"\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -1182,14 +1182,14 @@
"\tcellLayoutPaddingPx.bottom: 0.0px (0.0dp)\n" +
"\ticonSizePx: 141.0px (53.714287dp)\n" +
"\ticonTextSizePx: 34.0px (12.952381dp)\n" +
- "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+ "\ticonDrawablePaddingPx: 13.0px (4.952381dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 4\n" +
"\tfolderCellWidthPx: 189.0px (72.0dp)\n" +
"\tfolderCellHeightPx: 219.0px (83.42857dp)\n" +
"\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
"\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
- "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 5.0px (1.9047619dp)\n" +
"\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -1245,8 +1245,8 @@
"\tworkspacePadding.bottom: 330.0px (125.71429dp)\n" +
"\ticonScale: 1.0px (0.3809524dp)\n" +
"\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
- "\textraSpace: 478.0px (182.09525dp)\n" +
- "\tunscaled extraSpace: 478.0px (182.09525dp)\n" +
+ "\textraSpace: 498.0px (189.71428dp)\n" +
+ "\tunscaled extraSpace: 498.0px (189.71428dp)\n" +
"\tmaxEmptySpace: 0.0px (0.0dp)\n" +
"\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
"\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -1312,8 +1312,8 @@
"\tinv.numColumns: 4\n" +
"\tinv.numSearchContainerColumns: 4\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 159.0px (60.57143dp)\n" +
- "\tcellHeightPx: 223.0px (84.95238dp)\n" +
+ "\tcellWidthPx: 154.0px (58.666668dp)\n" +
+ "\tcellHeightPx: 218.0px (83.04762dp)\n" +
"\tgetCellSize().x: 270.0px (102.85714dp)\n" +
"\tgetCellSize().y: 342.0px (130.28572dp)\n" +
"\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -1324,14 +1324,14 @@
"\tcellLayoutPaddingPx.bottom: 0.0px (0.0dp)\n" +
"\ticonSizePx: 141.0px (53.714287dp)\n" +
"\ticonTextSizePx: 34.0px (12.952381dp)\n" +
- "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+ "\ticonDrawablePaddingPx: 13.0px (4.952381dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 4\n" +
"\tfolderCellWidthPx: 189.0px (72.0dp)\n" +
"\tfolderCellHeightPx: 219.0px (83.42857dp)\n" +
"\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
"\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
- "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 5.0px (1.9047619dp)\n" +
"\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -1387,8 +1387,8 @@
"\tworkspacePadding.bottom: 330.0px (125.71429dp)\n" +
"\ticonScale: 1.0px (0.3809524dp)\n" +
"\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
- "\textraSpace: 478.0px (182.09525dp)\n" +
- "\tunscaled extraSpace: 478.0px (182.09525dp)\n" +
+ "\textraSpace: 498.0px (189.71428dp)\n" +
+ "\tunscaled extraSpace: 498.0px (189.71428dp)\n" +
"\tmaxEmptySpace: 0.0px (0.0dp)\n" +
"\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
"\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -1454,8 +1454,8 @@
"\tinv.numColumns: 4\n" +
"\tinv.numSearchContainerColumns: 4\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 159.0px (60.57143dp)\n" +
- "\tcellHeightPx: 223.0px (84.95238dp)\n" +
+ "\tcellWidthPx: 154.0px (58.666668dp)\n" +
+ "\tcellHeightPx: 218.0px (83.04762dp)\n" +
"\tgetCellSize().x: 224.0px (85.333336dp)\n" +
"\tgetCellSize().y: 430.0px (163.80952dp)\n" +
"\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -1466,14 +1466,14 @@
"\tcellLayoutPaddingPx.bottom: 0.0px (0.0dp)\n" +
"\ticonSizePx: 141.0px (53.714287dp)\n" +
"\ticonTextSizePx: 34.0px (12.952381dp)\n" +
- "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+ "\ticonDrawablePaddingPx: 13.0px (4.952381dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 4\n" +
"\tfolderCellWidthPx: 189.0px (72.0dp)\n" +
"\tfolderCellHeightPx: 219.0px (83.42857dp)\n" +
"\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
"\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
- "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 5.0px (1.9047619dp)\n" +
"\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -1529,8 +1529,8 @@
"\tworkspacePadding.bottom: 330.0px (125.71429dp)\n" +
"\ticonScale: 1.0px (0.3809524dp)\n" +
"\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
- "\textraSpace: 829.0px (315.8095dp)\n" +
- "\tunscaled extraSpace: 829.0px (315.8095dp)\n" +
+ "\textraSpace: 849.0px (323.42856dp)\n" +
+ "\tunscaled extraSpace: 849.0px (323.42856dp)\n" +
"\tmaxEmptySpace: 0.0px (0.0dp)\n" +
"\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
"\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -1592,8 +1592,8 @@
"\tinv.numColumns: 4\n" +
"\tinv.numSearchContainerColumns: 4\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 159.0px (60.57143dp)\n" +
- "\tcellHeightPx: 223.0px (84.95238dp)\n" +
+ "\tcellWidthPx: 154.0px (58.666668dp)\n" +
+ "\tcellHeightPx: 218.0px (83.04762dp)\n" +
"\tgetCellSize().x: 224.0px (85.333336dp)\n" +
"\tgetCellSize().y: 430.0px (163.80952dp)\n" +
"\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -1604,14 +1604,14 @@
"\tcellLayoutPaddingPx.bottom: 0.0px (0.0dp)\n" +
"\ticonSizePx: 141.0px (53.714287dp)\n" +
"\ticonTextSizePx: 34.0px (12.952381dp)\n" +
- "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+ "\ticonDrawablePaddingPx: 13.0px (4.952381dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 4\n" +
"\tfolderCellWidthPx: 189.0px (72.0dp)\n" +
"\tfolderCellHeightPx: 219.0px (83.42857dp)\n" +
"\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
"\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
- "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 5.0px (1.9047619dp)\n" +
"\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -1667,8 +1667,8 @@
"\tworkspacePadding.bottom: 330.0px (125.71429dp)\n" +
"\ticonScale: 1.0px (0.3809524dp)\n" +
"\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
- "\textraSpace: 829.0px (315.8095dp)\n" +
- "\tunscaled extraSpace: 829.0px (315.8095dp)\n" +
+ "\textraSpace: 849.0px (323.42856dp)\n" +
+ "\tunscaled extraSpace: 849.0px (323.42856dp)\n" +
"\tmaxEmptySpace: 0.0px (0.0dp)\n" +
"\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
"\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
new file mode 100644
index 0000000..e40fb79
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
@@ -0,0 +1,91 @@
+/*
+ * 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.viewcapture_analysis;
+
+import static com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.diagPathFromRoot;
+
+import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnalysisNode;
+import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnomalyDetector;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Anomaly detector that triggers an error when alpha of a view changes too rapidly.
+ * Invisible views are treated as if they had zero alpha.
+ */
+final class AlphaJumpDetector extends AnomalyDetector {
+ // Paths of nodes that are excluded from analysis.
+ private static final Collection<String> PATHS_TO_IGNORE = Set.of(
+ "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+ + ":id/drag_layer|SearchContainerView:id/apps_view|SearchRecyclerView:id"
+ + "/search_results_list_view|SearchResultSmallIconRow",
+ "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+ + ":id/drag_layer|SearchContainerView:id/apps_view|SearchRecyclerView:id"
+ + "/search_results_list_view|SearchResultIcon",
+ "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+ + ":id/drag_layer|LauncherRecentsView:id/overview_panel|TaskView",
+ "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+ + ":id/drag_layer|WidgetsFullSheet|SpringRelativeLayout:id/container"
+ + "|WidgetsRecyclerView:id/primary_widgets_list_view|WidgetsListHeader:id"
+ + "/widgets_list_header",
+ "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+ + ":id/drag_layer|WidgetsFullSheet|SpringRelativeLayout:id/container"
+ + "|WidgetsRecyclerView:id/primary_widgets_list_view"
+ + "|StickyHeaderLayout$EmptySpaceView",
+ "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+ + ":id/drag_layer|SearchContainerView:id/apps_view|AllAppsRecyclerView:id"
+ + "/apps_list_view|BubbleTextView:id/icon",
+ "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+ + ":id/drag_layer|LauncherRecentsView:id/overview_panel|ClearAllButton:id"
+ + "/clear_all",
+ "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+ + ":id/drag_layer|NexusOverviewActionsView:id/overview_actions_view"
+ + "|LinearLayout:id/action_buttons"
+ );
+ // Minimal increase or decrease of view's alpha between frames that triggers the error.
+ private static final float ALPHA_JUMP_THRESHOLD = 1f;
+
+ @Override
+ void initializeNode(AnalysisNode info) {
+ // If the parent view ignores alpha jumps, its descendants will too.
+ final boolean parentIgnoreAlphaJumps = info.parent != null && info.parent.ignoreAlphaJumps;
+ info.ignoreAlphaJumps = parentIgnoreAlphaJumps
+ || PATHS_TO_IGNORE.contains(diagPathFromRoot(info));
+ }
+
+ @Override
+ void detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN) {
+ // If the view was previously seen, proceed with analysis only if it was present in the
+ // view hierarchy in the previous frame.
+ if (oldInfo != null && oldInfo.frameN != frameN) return;
+
+ final AnalysisNode latestInfo = newInfo != null ? newInfo : oldInfo;
+ if (latestInfo.ignoreAlphaJumps) return;
+
+ final float oldAlpha = oldInfo != null ? oldInfo.alpha : 0;
+ final float newAlpha = newInfo != null ? newInfo.alpha : 0;
+ final float alphaDeltaAbs = Math.abs(newAlpha - oldAlpha);
+
+ if (alphaDeltaAbs >= ALPHA_JUMP_THRESHOLD) {
+ throw new AssertionError(
+ String.format(
+ "Alpha jump detected in ViewCapture data: alpha change: %s (%s -> %s)"
+ + ", threshold: %s, view: %s",
+ alphaDeltaAbs, oldAlpha, newAlpha, ALPHA_JUMP_THRESHOLD, latestInfo));
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java
new file mode 100644
index 0000000..5a2611c
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java
@@ -0,0 +1,238 @@
+/*
+ * 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.viewcapture_analysis;
+
+import static android.view.View.VISIBLE;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.app.viewcapture.data.ExportedData;
+import com.android.app.viewcapture.data.FrameData;
+import com.android.app.viewcapture.data.ViewNode;
+import com.android.app.viewcapture.data.WindowData;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Utility that analyzes ViewCapture data and finds anomalies such as views appearing or
+ * disappearing without alpha-fading.
+ */
+public class ViewCaptureAnalyzer {
+ private static final String SCRIM_VIEW_CLASS = "com.android.launcher3.views.ScrimView";
+
+ /**
+ * Detector of one kind of anomaly.
+ */
+ abstract static class AnomalyDetector {
+ /**
+ * Initializes fields of the node that are specific to the anomaly detected by this
+ * detector.
+ */
+ abstract void initializeNode(@NonNull AnalysisNode info);
+
+ /**
+ * Detects anomalies by looking at the last occurrence of a view, and the current one.
+ * null value means that the view. 'oldInfo' and 'newInfo' cannot be both null.
+ * If an anomaly is detected, an exception will be thrown.
+ *
+ * @param oldInfo the view, as seen in the last frame that contained it in the view
+ * hierarchy before 'currentFrame'. 'null' means that the view is first seen
+ * in the 'currentFrame'.
+ * @param newInfo the view in the view hierarchy of the 'currentFrame'. 'null' means that
+ * the view is not present in the 'currentFrame', but was present in earlier
+ * frames.
+ * @param frameN number of the current frame.
+ */
+ abstract void detectAnomalies(
+ @Nullable AnalysisNode oldInfo, @Nullable AnalysisNode newInfo, int frameN);
+ }
+
+ // All detectors. They will be invoked in the order listed here.
+ private static final Iterable<AnomalyDetector> ANOMALY_DETECTORS = Arrays.asList(
+ new AlphaJumpDetector()
+ );
+
+ // A view from view capture data converted to a form that's convenient for detecting anomalies.
+ static class AnalysisNode {
+ public String className;
+ public String resourceId;
+ public AnalysisNode parent;
+
+ // Window coordinates of the view.
+ public float left;
+ public float top;
+
+ // Visible scale and alpha, build recursively from the ancestor list.
+ public float scaleX;
+ public float scaleY;
+ public float alpha;
+
+ public int frameN;
+ public ViewNode viewCaptureNode;
+
+ public boolean ignoreAlphaJumps;
+
+ @Override
+ public String toString() {
+ return String.format("window coordinates: (%s, %s), class path from the root: %s",
+ left, top, diagPathFromRoot(this));
+ }
+ }
+
+ /**
+ * Scans a view capture record and throws an error if an anomaly is found.
+ */
+ public static void assertNoAnomalies(ExportedData viewCaptureData) {
+ final int scrimClassIndex = viewCaptureData.getClassnameList().indexOf(SCRIM_VIEW_CLASS);
+
+ final int windowDataCount = viewCaptureData.getWindowDataCount();
+ for (int i = 0; i < windowDataCount; ++i) {
+ analyzeWindowData(viewCaptureData, viewCaptureData.getWindowData(i), scrimClassIndex);
+ }
+ }
+
+ private static void analyzeWindowData(ExportedData viewCaptureData, WindowData windowData,
+ int scrimClassIndex) {
+ // View hash code => Last seen node with this hash code.
+ // The view is added when we analyze the first frame where it's visible.
+ // After that, it gets updated for every frame where it's visible.
+ // As we go though frames, if a view becomes invisible, it stays in the map.
+ final Map<Integer, AnalysisNode> lastSeenNodes = new HashMap<>();
+
+ for (int frameN = 0; frameN < windowData.getFrameDataCount(); ++frameN) {
+ analyzeFrame(frameN, windowData.getFrameData(frameN), viewCaptureData, lastSeenNodes,
+ scrimClassIndex);
+ }
+ }
+
+ private static void analyzeFrame(int frameN, FrameData frame, ExportedData viewCaptureData,
+ Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex) {
+ // Analyze the node tree starting from the root.
+ analyzeView(
+ frame.getNode(),
+ /* parent = */ null,
+ frameN,
+ /* leftShift = */ 0,
+ /* topShift = */ 0,
+ viewCaptureData,
+ lastSeenNodes,
+ scrimClassIndex);
+
+ // Analyze transitions when a view visible in the last frame become invisible in the
+ // current one.
+ for (AnalysisNode info : lastSeenNodes.values()) {
+ if (info.frameN == frameN - 1) {
+ if (!info.viewCaptureNode.getWillNotDraw()) {
+ ANOMALY_DETECTORS.forEach(
+ detector -> detector.detectAnomalies(
+ /* oldInfo = */ info,
+ /* newInfo = */ null,
+ frameN));
+ }
+ }
+ }
+ }
+
+ private static void analyzeView(ViewNode viewCaptureNode, AnalysisNode parent, int frameN,
+ float leftShift, float topShift, ExportedData viewCaptureData,
+ Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex) {
+ // Skip analysis of invisible views
+ final float parentAlpha = parent != null ? parent.alpha : 1;
+ final float alpha = getVisibleAlpha(viewCaptureNode, parentAlpha);
+ if (alpha <= 0.0) return;
+
+ // Calculate analysis node parameters
+ final int hashcode = viewCaptureNode.getHashcode();
+ final int classIndex = viewCaptureNode.getClassnameIndex();
+
+ final float parentScaleX = parent != null ? parent.scaleX : 1;
+ final float parentScaleY = parent != null ? parent.scaleY : 1;
+ final float scaleX = parentScaleX * viewCaptureNode.getScaleX();
+ final float scaleY = parentScaleY * viewCaptureNode.getScaleY();
+
+ final float left = leftShift
+ + (viewCaptureNode.getLeft() + viewCaptureNode.getTranslationX()) * parentScaleX
+ + viewCaptureNode.getWidth() * (parentScaleX - scaleX) / 2;
+ final float top = topShift
+ + (viewCaptureNode.getTop() + viewCaptureNode.getTranslationY()) * parentScaleY
+ + viewCaptureNode.getHeight() * (parentScaleY - scaleY) / 2;
+
+ // Initialize new analysis node
+ final AnalysisNode newAnalysisNode = new AnalysisNode();
+ newAnalysisNode.className = viewCaptureData.getClassname(classIndex);
+ newAnalysisNode.resourceId = viewCaptureNode.getId();
+ newAnalysisNode.parent = parent;
+ newAnalysisNode.left = left;
+ newAnalysisNode.top = top;
+ newAnalysisNode.scaleX = scaleX;
+ newAnalysisNode.scaleY = scaleY;
+ newAnalysisNode.alpha = alpha;
+ newAnalysisNode.frameN = frameN;
+ newAnalysisNode.viewCaptureNode = viewCaptureNode;
+ ANOMALY_DETECTORS.forEach(detector -> detector.initializeNode(newAnalysisNode));
+
+ // Detect anomalies for the view
+ final AnalysisNode oldAnalysisNode = lastSeenNodes.get(hashcode); // may be null
+ if (frameN != 0 && !viewCaptureNode.getWillNotDraw()) {
+ ANOMALY_DETECTORS.forEach(
+ detector -> detector.detectAnomalies(oldAnalysisNode, newAnalysisNode, frameN));
+ }
+ lastSeenNodes.put(hashcode, newAnalysisNode);
+
+ // Enumerate children starting from the topmost one. Stop at ScrimView, if present.
+ final float leftShiftForChildren = left - viewCaptureNode.getScrollX();
+ final float topShiftForChildren = top - viewCaptureNode.getScrollY();
+ for (int i = viewCaptureNode.getChildrenCount() - 1; i >= 0; --i) {
+ final ViewNode child = viewCaptureNode.getChildren(i);
+
+ // Don't analyze anything under scrim view because we don't know whether it's
+ // transparent.
+ if (child.getClassnameIndex() == scrimClassIndex) break;
+
+ analyzeView(child, newAnalysisNode, frameN, leftShiftForChildren, topShiftForChildren,
+ viewCaptureData, lastSeenNodes,
+ scrimClassIndex);
+ }
+ }
+
+ private static float getVisibleAlpha(ViewNode node, float parenVisibleAlpha) {
+ return node.getVisibility() == VISIBLE
+ ? parenVisibleAlpha * Math.max(0, Math.min(node.getAlpha(), 1))
+ : 0f;
+ }
+
+ private static String classNameToSimpleName(String className) {
+ return className.substring(className.lastIndexOf(".") + 1);
+ }
+
+ static String diagPathFromRoot(AnalysisNode nodeBox) {
+ final StringBuilder path = new StringBuilder(diagPathElement(nodeBox));
+ for (AnalysisNode ancestor = nodeBox.parent; ancestor != null; ancestor = ancestor.parent) {
+ path.insert(0, diagPathElement(ancestor) + "|");
+ }
+ return path.toString();
+ }
+
+ private static String diagPathElement(AnalysisNode nodeBox) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(classNameToSimpleName(nodeBox.className));
+ if (!"NO_ID".equals(nodeBox.resourceId)) sb.append(":" + nodeBox.resourceId);
+ return sb.toString();
+ }
+}