Merge changes from topic "215584502" into tm-qpr-dev
* changes: Add unit tests for combined QS headers New headers
This commit is contained in:
committed by
Android (Google) Code Review
commit
77023ce014
@@ -14,7 +14,7 @@
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<androidx.constraintlayout.motion.widget.MotionLayout
|
||||
<com.android.systemui.util.NoRemeasureMotionLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/split_shade_status_bar"
|
||||
@@ -32,10 +32,37 @@
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/center"
|
||||
app:layout_constraintGuide_percent="0.5"
|
||||
android:id="@+id/begin_guide"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_begin="0dp"/>
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/end_guide"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_end="0dp"
|
||||
/>
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/center_left"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/center_right"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/barrier"
|
||||
app:barrierDirection="start"
|
||||
app:constraint_referenced_ids="statusIcons,privacy_container" />
|
||||
|
||||
<com.android.systemui.statusbar.policy.Clock
|
||||
android:id="@+id/clock"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -44,18 +71,25 @@
|
||||
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
|
||||
android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
|
||||
android:singleLine="true"
|
||||
android:textDirection="locale"
|
||||
android:textAppearance="@style/TextAppearance.QS.Status"
|
||||
android:transformPivotX="0sp"
|
||||
android:transformPivotY="20sp"
|
||||
android:scaleX="1"
|
||||
android:scaleY="1"
|
||||
/>
|
||||
|
||||
<com.android.systemui.statusbar.policy.DateView
|
||||
<com.android.systemui.statusbar.policy.VariableDateView
|
||||
android:id="@+id/date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:textDirection="locale"
|
||||
android:textAppearance="@style/TextAppearance.QS.Status"
|
||||
app:datePattern="@string/abbrev_wday_month_day_no_year_alarm"
|
||||
app:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
|
||||
app:shortDatePattern="@string/abbrev_month_day_no_year"
|
||||
/>
|
||||
|
||||
<include
|
||||
@@ -81,7 +115,7 @@
|
||||
app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
|
||||
android:paddingEnd="@dimen/signal_cluster_battery_padding"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_height="@dimen/large_screen_shade_header_min_height"
|
||||
app:layout_constraintStart_toEndOf="@id/carrier_group"
|
||||
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
|
||||
app:layout_constraintTop_toTopOf="@id/clock"
|
||||
@@ -92,8 +126,9 @@
|
||||
<com.android.systemui.battery.BatteryMeterView
|
||||
android:id="@+id/batteryRemainingIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_height="@dimen/large_screen_shade_header_min_height"
|
||||
app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:textAppearance="@style/TextAppearance.QS.Status"
|
||||
app:layout_constraintStart_toEndOf="@id/statusIcons"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@@ -104,13 +139,18 @@
|
||||
<FrameLayout
|
||||
android:id="@+id/privacy_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_height="@dimen/large_screen_shade_header_min_height"
|
||||
android:gravity="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/end_guide"
|
||||
app:layout_constraintTop_toTopOf="@id/date"
|
||||
app:layout_constraintBottom_toBottomOf="@id/date"
|
||||
>
|
||||
<include layout="@layout/ongoing_privacy_chip"/>
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.constraintlayout.motion.widget.MotionLayout>
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:id="@+id/space"
|
||||
/>
|
||||
</com.android.systemui.util.NoRemeasureMotionLayout>
|
||||
@@ -553,7 +553,7 @@
|
||||
<dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
|
||||
<dimen name="qs_panel_elevation">4dp</dimen>
|
||||
<dimen name="qs_panel_padding_bottom">@dimen/footer_actions_height</dimen>
|
||||
<dimen name="qs_panel_padding_top">48dp</dimen>
|
||||
<dimen name="qs_panel_padding_top">80dp</dimen>
|
||||
|
||||
<dimen name="qs_data_usage_text_size">14sp</dimen>
|
||||
<dimen name="qs_data_usage_usage_text_size">36sp</dimen>
|
||||
@@ -564,6 +564,8 @@
|
||||
<dimen name="qs_footer_icon_size">20dp</dimen>
|
||||
<dimen name="qs_header_row_min_height">48dp</dimen>
|
||||
|
||||
<dimen name="qs_header_non_clickable_element_height">24dp</dimen>
|
||||
|
||||
<dimen name="qs_footer_padding">20dp</dimen>
|
||||
<dimen name="qs_security_footer_height">88dp</dimen>
|
||||
<dimen name="qs_security_footer_single_line_height">48dp</dimen>
|
||||
|
||||
@@ -25,20 +25,109 @@
|
||||
<KeyFrameSet>
|
||||
<!-- These positions are to prevent visual movement of @id/date -->
|
||||
<KeyPosition
|
||||
app:keyPositionType="pathRelative"
|
||||
app:keyPositionType="deltaRelative"
|
||||
app:percentX="0"
|
||||
app:percentY="0"
|
||||
app:framePosition="49"
|
||||
app:percentWidth="1"
|
||||
app:percentHeight="1"
|
||||
app:curveFit="linear"
|
||||
app:motionTarget="@id/date" />
|
||||
<KeyPosition
|
||||
app:keyPositionType="pathRelative"
|
||||
app:keyPositionType="deltaRelative"
|
||||
app:percentX="1"
|
||||
app:percentY="0.51"
|
||||
app:framePosition="51"
|
||||
app:percentWidth="1"
|
||||
app:percentHeight="1"
|
||||
app:curveFit="linear"
|
||||
app:motionTarget="@id/date" />
|
||||
<KeyAttribute
|
||||
app:motionTarget="@id/date"
|
||||
app:framePosition="50"
|
||||
app:framePosition="30"
|
||||
android:alpha="0"
|
||||
/>
|
||||
<KeyAttribute
|
||||
app:motionTarget="@id/date"
|
||||
app:framePosition="70"
|
||||
android:alpha="0"
|
||||
/>
|
||||
<KeyPosition
|
||||
app:keyPositionType="pathRelative"
|
||||
app:percentX="0"
|
||||
app:percentY="0"
|
||||
app:framePosition="0"
|
||||
app:curveFit="linear"
|
||||
app:motionTarget="@id/statusIcons" />
|
||||
<KeyPosition
|
||||
app:keyPositionType="pathRelative"
|
||||
app:percentX="0"
|
||||
app:percentY="0"
|
||||
app:framePosition="50"
|
||||
app:curveFit="linear"
|
||||
app:motionTarget="@id/statusIcons" />
|
||||
<KeyPosition
|
||||
app:keyPositionType="deltaRelative"
|
||||
app:percentX="1"
|
||||
app:percentY="0.51"
|
||||
app:framePosition="51"
|
||||
app:curveFit="linear"
|
||||
app:motionTarget="@id/statusIcons" />
|
||||
<KeyAttribute
|
||||
app:motionTarget="@id/statusIcons"
|
||||
app:framePosition="30"
|
||||
android:alpha="0"
|
||||
/>
|
||||
<KeyAttribute
|
||||
app:motionTarget="@id/statusIcons"
|
||||
app:framePosition="70"
|
||||
android:alpha="0"
|
||||
/>
|
||||
<KeyPosition
|
||||
app:keyPositionType="deltaRelative"
|
||||
app:percentX="0"
|
||||
app:percentY="0"
|
||||
app:framePosition="50"
|
||||
app:percentWidth="1"
|
||||
app:percentHeight="1"
|
||||
app:curveFit="linear"
|
||||
app:motionTarget="@id/batteryRemainingIcon" />
|
||||
<KeyPosition
|
||||
app:keyPositionType="deltaRelative"
|
||||
app:percentX="1"
|
||||
app:percentY="0.51"
|
||||
app:framePosition="51"
|
||||
app:percentWidth="1"
|
||||
app:percentHeight="1"
|
||||
app:curveFit="linear"
|
||||
app:motionTarget="@id/batteryRemainingIcon" />
|
||||
<KeyAttribute
|
||||
app:motionTarget="@id/batteryRemainingIcon"
|
||||
app:framePosition="30"
|
||||
android:alpha="0"
|
||||
/>
|
||||
<KeyAttribute
|
||||
app:motionTarget="@id/batteryRemainingIcon"
|
||||
app:framePosition="70"
|
||||
android:alpha="0"
|
||||
/>
|
||||
<KeyPosition
|
||||
app:motionTarget="@id/carrier_group"
|
||||
app:percentX="1"
|
||||
app:percentY="0.51"
|
||||
app:framePosition="51"
|
||||
app:percentWidth="1"
|
||||
app:percentHeight="1"
|
||||
app:curveFit="linear"
|
||||
app:keyPositionType="deltaRelative" />
|
||||
<KeyAttribute
|
||||
app:motionTarget="@id/carrier_group"
|
||||
app:framePosition="0"
|
||||
android:alpha="0" />
|
||||
<KeyAttribute
|
||||
app:motionTarget="@id/carrier_group"
|
||||
app:framePosition="70"
|
||||
android:alpha="0" />
|
||||
</KeyFrameSet>
|
||||
</Transition>
|
||||
|
||||
|
||||
@@ -29,7 +29,12 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/date"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
/>
|
||||
<Transform
|
||||
android:scaleX="1"
|
||||
android:scaleY="1"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
<Constraint
|
||||
@@ -47,9 +52,38 @@
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/carrier_group">
|
||||
<Layout
|
||||
app:layout_constraintWidth_min="48dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constrainedWidth="true"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_constraintStart_toEndOf="@id/date"
|
||||
app:layout_constraintEnd_toStartOf="@id/statusIcons"
|
||||
app:layout_constraintTop_toTopOf="@id/clock"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
/>
|
||||
<PropertySet
|
||||
android:alpha="1"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/statusIcons">
|
||||
<Layout
|
||||
app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/large_screen_shade_header_min_height"
|
||||
app:layout_constraintStart_toEndOf="@id/carrier_group"
|
||||
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
|
||||
app:layout_constraintTop_toTopOf="@id/clock"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
/>
|
||||
<PropertySet
|
||||
android:alpha="1"
|
||||
app:customFloatValue="1"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
@@ -64,6 +98,9 @@
|
||||
app:layout_constraintTop_toTopOf="@id/clock"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
/>
|
||||
<PropertySet
|
||||
android:alpha="1"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
<Constraint
|
||||
@@ -75,6 +112,7 @@
|
||||
app:layout_constraintTop_toTopOf="@id/date"
|
||||
app:layout_constraintBottom_toBottomOf="@id/date"
|
||||
app:layout_constraintStart_toEndOf="@id/batteryRemainingIcon"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
|
||||
@@ -26,22 +26,27 @@
|
||||
<Layout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/begin_guide"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/date"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
/>
|
||||
<Transform
|
||||
android:scaleX="1"
|
||||
android:scaleY="1"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/date">
|
||||
<Layout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintStart_toEndOf="@id/clock"
|
||||
app:layout_constraintEnd_toStartOf="@id/carrier_group"
|
||||
app:layout_constraintEnd_toStartOf="@id/barrier"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
@@ -50,17 +55,41 @@
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/statusIcons">
|
||||
<Layout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/qs_header_non_clickable_element_height"
|
||||
app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
|
||||
app:layout_constraintStart_toEndOf="@id/date"
|
||||
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
|
||||
app:layout_constraintTop_toTopOf="@id/date"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/batteryRemainingIcon" >
|
||||
android:id="@+id/batteryRemainingIcon">
|
||||
<Layout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/qs_header_non_clickable_element_height"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
|
||||
app:layout_constraintStart_toEndOf="@id/statusIcons"
|
||||
app:layout_constraintEnd_toEndOf="@id/end_guide"
|
||||
app:layout_constraintTop_toTopOf="@id/date"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/carrier_group">
|
||||
<CustomAttribute
|
||||
app:attributeName="alpha"
|
||||
app:customFloatValue="0"
|
||||
<Layout
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
<PropertySet
|
||||
android:alpha="0"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
@@ -69,9 +98,11 @@
|
||||
<Layout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/date"
|
||||
app:layout_constraintEnd_toEndOf="@id/end_guide"
|
||||
app:layout_constraintTop_toTopOf="@id/date"
|
||||
app:layout_constraintBottom_toBottomOf="@id/date"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
|
||||
124
packages/SystemUI/res/xml/qs_header_new.xml
Normal file
124
packages/SystemUI/res/xml/qs_header_new.xml
Normal file
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2021 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<ConstraintSet
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/qs_header_constraint"
|
||||
>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/privacy_container">
|
||||
<Layout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/large_screen_shade_header_min_height"
|
||||
app:layout_constraintEnd_toEndOf="@id/end_guide"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/carrier_group"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/clock">
|
||||
<Layout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/large_screen_shade_header_min_height"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/privacy_container"
|
||||
app:layout_constraintBottom_toTopOf="@id/date"
|
||||
app:layout_constraintEnd_toStartOf="@id/carrier_group"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
/>
|
||||
<Transform
|
||||
android:scaleX="2.4"
|
||||
android:scaleY="2.4"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/date">
|
||||
<Layout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/qs_header_non_clickable_element_height"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/space"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/clock"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/carrier_group">
|
||||
<Layout
|
||||
app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
|
||||
android:minHeight="@dimen/large_screen_shade_header_min_height"
|
||||
app:layout_constraintWidth_min="48dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintStart_toEndOf="@id/clock"
|
||||
app:layout_constraintTop_toBottomOf="@id/privacy_container"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
|
||||
/>
|
||||
<PropertySet
|
||||
android:alpha="1"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/statusIcons">
|
||||
<Layout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/qs_header_non_clickable_element_height"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintStart_toEndOf="@id/space"
|
||||
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
|
||||
app:layout_constraintTop_toTopOf="@id/date"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/batteryRemainingIcon">
|
||||
<Layout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/qs_header_non_clickable_element_height"
|
||||
app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
|
||||
app:layout_constraintStart_toEndOf="@id/statusIcons"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/date"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
/>
|
||||
</Constraint>
|
||||
|
||||
|
||||
<Constraint
|
||||
android:id="@id/space">
|
||||
<Layout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintStart_toEndOf="@id/date"
|
||||
app:layout_constraintEnd_toStartOf="@id/statusIcons"
|
||||
/>
|
||||
</Constraint>
|
||||
</ConstraintSet>
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.systemui.shade
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
|
||||
typealias ConstraintChange = ConstraintSet.() -> Unit
|
||||
|
||||
operator fun ConstraintChange?.plus(other: ConstraintChange?): ConstraintChange? {
|
||||
// Prevent wrapping
|
||||
if (this == null) return other
|
||||
if (other == null) return this
|
||||
else return {
|
||||
this@plus()
|
||||
other()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains all changes that need to be performed to the different [ConstraintSet] in
|
||||
* [LargeScreenShadeHeaderController].
|
||||
*/
|
||||
data class ConstraintsChanges(
|
||||
val qqsConstraintsChanges: ConstraintChange? = null,
|
||||
val qsConstraintsChanges: ConstraintChange? = null,
|
||||
val largeScreenConstraintsChanges: ConstraintChange? = null
|
||||
) {
|
||||
operator fun plus(other: ConstraintsChanges) = ConstraintsChanges(
|
||||
qqsConstraintsChanges + other.qqsConstraintsChanges,
|
||||
qsConstraintsChanges + other.qsConstraintsChanges,
|
||||
largeScreenConstraintsChanges + other.largeScreenConstraintsChanges
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines [ConstraintChanges] for [LargeScreenShadeHeaderController] based on configurations.
|
||||
*
|
||||
* Given that the number of different scenarios is not that large, having specific methods instead
|
||||
* of a full map between state and [ConstraintSet] was preferred.
|
||||
*/
|
||||
interface CombinedShadeHeadersConstraintManager {
|
||||
/**
|
||||
* Changes for when the visibility of the privacy chip changes
|
||||
*/
|
||||
fun privacyChipVisibilityConstraints(visible: Boolean): ConstraintsChanges
|
||||
|
||||
/**
|
||||
* Changes for situations with no top center cutout (there may be a corner cutout)
|
||||
*/
|
||||
fun emptyCutoutConstraints(): ConstraintsChanges
|
||||
|
||||
/**
|
||||
* Changes to incorporate side insets due to rounded corners/corner cutouts
|
||||
*/
|
||||
fun edgesGuidelinesConstraints(
|
||||
cutoutStart: Int,
|
||||
paddingStart: Int,
|
||||
cutoutEnd: Int,
|
||||
paddingEnd: Int
|
||||
): ConstraintsChanges
|
||||
|
||||
/**
|
||||
* Changes for situations with top center cutout (in this case, there are no corner cutouts).
|
||||
*/
|
||||
fun centerCutoutConstraints(rtl: Boolean, offsetFromEdge: Int): ConstraintsChanges
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.shade
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
|
||||
|
||||
/**
|
||||
* Standard implementation of [CombinedShadeHeadersConstraintManager].
|
||||
*/
|
||||
@CentralSurfacesComponent.CentralSurfacesScope
|
||||
object CombinedShadeHeadersConstraintManagerImpl : CombinedShadeHeadersConstraintManager {
|
||||
|
||||
override fun privacyChipVisibilityConstraints(visible: Boolean): ConstraintsChanges {
|
||||
val constraintAlpha = if (visible) 0f else 1f
|
||||
return ConstraintsChanges(
|
||||
qqsConstraintsChanges = {
|
||||
setAlpha(R.id.statusIcons, constraintAlpha)
|
||||
setAlpha(R.id.batteryRemainingIcon, constraintAlpha)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun emptyCutoutConstraints(): ConstraintsChanges {
|
||||
return ConstraintsChanges(
|
||||
qqsConstraintsChanges = {
|
||||
connect(R.id.date, ConstraintSet.END, R.id.barrier, ConstraintSet.START)
|
||||
createBarrier(
|
||||
R.id.barrier,
|
||||
ConstraintSet.START,
|
||||
0,
|
||||
R.id.statusIcons,
|
||||
R.id.privacy_container
|
||||
)
|
||||
connect(R.id.statusIcons, ConstraintSet.START, R.id.date, ConstraintSet.END)
|
||||
connect(R.id.privacy_container, ConstraintSet.START, R.id.date, ConstraintSet.END)
|
||||
constrainWidth(R.id.statusIcons, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun edgesGuidelinesConstraints(
|
||||
cutoutStart: Int,
|
||||
paddingStart: Int,
|
||||
cutoutEnd: Int,
|
||||
paddingEnd: Int
|
||||
): ConstraintsChanges {
|
||||
val change: ConstraintChange = {
|
||||
setGuidelineBegin(R.id.begin_guide, Math.max(cutoutStart - paddingStart, 0))
|
||||
setGuidelineEnd(R.id.end_guide, Math.max(cutoutEnd - paddingEnd, 0))
|
||||
}
|
||||
return ConstraintsChanges(
|
||||
qqsConstraintsChanges = change,
|
||||
qsConstraintsChanges = change
|
||||
)
|
||||
}
|
||||
|
||||
override fun centerCutoutConstraints(rtl: Boolean, offsetFromEdge: Int): ConstraintsChanges {
|
||||
val centerStart = if (!rtl) R.id.center_left else R.id.center_right
|
||||
val centerEnd = if (!rtl) R.id.center_right else R.id.center_left
|
||||
// Use guidelines to block the center cutout area.
|
||||
return ConstraintsChanges(
|
||||
qqsConstraintsChanges = {
|
||||
setGuidelineBegin(centerStart, offsetFromEdge)
|
||||
setGuidelineEnd(centerEnd, offsetFromEdge)
|
||||
connect(R.id.date, ConstraintSet.END, centerStart, ConstraintSet.START)
|
||||
connect(
|
||||
R.id.statusIcons,
|
||||
ConstraintSet.START,
|
||||
centerEnd,
|
||||
ConstraintSet.END
|
||||
)
|
||||
connect(
|
||||
R.id.privacy_container,
|
||||
ConstraintSet.START,
|
||||
centerEnd,
|
||||
ConstraintSet.END
|
||||
)
|
||||
constrainWidth(R.id.statusIcons, 0)
|
||||
},
|
||||
qsConstraintsChanges = {
|
||||
setGuidelineBegin(centerStart, offsetFromEdge)
|
||||
setGuidelineEnd(centerEnd, offsetFromEdge)
|
||||
connect(
|
||||
R.id.privacy_container,
|
||||
ConstraintSet.START,
|
||||
centerEnd,
|
||||
ConstraintSet.END
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.shade
|
||||
|
||||
import android.annotation.IdRes
|
||||
import android.app.StatusBarManager
|
||||
import android.content.res.Configuration
|
||||
import android.os.Trace
|
||||
import android.os.Trace.TRACE_TAG_APP
|
||||
import android.util.Pair
|
||||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
import com.android.settingslib.Utils
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.animation.ShadeInterpolation
|
||||
import com.android.systemui.battery.BatteryMeterView
|
||||
import com.android.systemui.battery.BatteryMeterViewController
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.qs.ChipVisibilityListener
|
||||
import com.android.systemui.qs.HeaderPrivacyIconsController
|
||||
import com.android.systemui.qs.carrier.QSCarrierGroup
|
||||
import com.android.systemui.qs.carrier.QSCarrierGroupController
|
||||
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
|
||||
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
|
||||
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
|
||||
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
|
||||
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
|
||||
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController
|
||||
import com.android.systemui.statusbar.phone.StatusIconContainer
|
||||
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
|
||||
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
|
||||
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.statusbar.policy.VariableDateView
|
||||
import com.android.systemui.statusbar.policy.VariableDateViewController
|
||||
import com.android.systemui.util.ViewController
|
||||
import java.io.PrintWriter
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
/**
|
||||
* Controller for QS header on Large Screen width (large screen + landscape).
|
||||
*
|
||||
* Additionally, this serves as the staging ground for the combined QS headers. A single
|
||||
* [MotionLayout] that changes constraints depending on the configuration and can animate the
|
||||
* expansion of the headers in small screen portrait.
|
||||
*
|
||||
* [header] will be a [MotionLayout] if [Flags.COMBINED_QS_HEADERS] is enabled. In this case, the
|
||||
* [MotionLayout] has 2 transitions:
|
||||
* * [HEADER_TRANSITION_ID]: [QQS_HEADER_CONSTRAINT] <-> [QS_HEADER_CONSTRAINT] for portrait
|
||||
* handheld device configuration.
|
||||
* * [LARGE_SCREEN_HEADER_TRANSITION_ID]: [LARGE_SCREEN_HEADER_CONSTRAINT] (to itself) for all
|
||||
* other configurations
|
||||
*/
|
||||
@CentralSurfacesScope
|
||||
class LargeScreenShadeHeaderController @Inject constructor(
|
||||
@Named(LARGE_SCREEN_SHADE_HEADER) private val header: View,
|
||||
private val statusBarIconController: StatusBarIconController,
|
||||
private val privacyIconsController: HeaderPrivacyIconsController,
|
||||
private val insetsProvider: StatusBarContentInsetsProvider,
|
||||
private val configurationController: ConfigurationController,
|
||||
private val variableDateViewControllerFactory: VariableDateViewController.Factory,
|
||||
@Named(LARGE_SCREEN_BATTERY_CONTROLLER)
|
||||
private val batteryMeterViewController: BatteryMeterViewController,
|
||||
private val dumpManager: DumpManager,
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
|
||||
private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager
|
||||
) : ViewController<View>(header), Dumpable {
|
||||
|
||||
companion object {
|
||||
/** IDs for transitions and constraints for the [MotionLayout]. These are only used when
|
||||
* [Flags.COMBINED_QS_HEADERS] is enabled.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
internal val HEADER_TRANSITION_ID = R.id.header_transition
|
||||
@VisibleForTesting
|
||||
internal val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
|
||||
@VisibleForTesting
|
||||
internal val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
|
||||
@VisibleForTesting
|
||||
internal val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
|
||||
@VisibleForTesting
|
||||
internal val LARGE_SCREEN_HEADER_CONSTRAINT = R.id.large_screen_header_constraint
|
||||
|
||||
private fun Int.stateToString() = when (this) {
|
||||
QQS_HEADER_CONSTRAINT -> "QQS Header"
|
||||
QS_HEADER_CONSTRAINT -> "QS Header"
|
||||
LARGE_SCREEN_HEADER_CONSTRAINT -> "Large Screen Header"
|
||||
else -> "Unknown state"
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
loadConstraints()
|
||||
}
|
||||
|
||||
private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
|
||||
|
||||
private lateinit var iconManager: StatusBarIconController.TintedIconManager
|
||||
private lateinit var carrierIconSlots: List<String>
|
||||
private lateinit var qsCarrierGroupController: QSCarrierGroupController
|
||||
|
||||
private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
|
||||
private val clock: TextView = header.findViewById(R.id.clock)
|
||||
private val date: TextView = header.findViewById(R.id.date)
|
||||
private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
|
||||
private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
|
||||
|
||||
private var cutoutLeft = 0
|
||||
private var cutoutRight = 0
|
||||
private var roundedCorners = 0
|
||||
private var lastInsets: WindowInsets? = null
|
||||
|
||||
private var qsDisabled = false
|
||||
private var visible = false
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
field = value
|
||||
updateListeners()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the QQS/QS part of the shade is visible. This is particularly important in
|
||||
* Lockscreen, as the shade is visible but QS is not.
|
||||
*/
|
||||
var qsVisible = false
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
field = value
|
||||
onShadeExpandedChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we are in a configuration with large screen width. In this case, the header is a
|
||||
* single line.
|
||||
*/
|
||||
var largeScreenActive = false
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
field = value
|
||||
onHeaderStateChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Expansion fraction of the QQS/QS shade. This is not the expansion between QQS <-> QS.
|
||||
*/
|
||||
var shadeExpandedFraction = -1f
|
||||
set(value) {
|
||||
if (visible && field != value) {
|
||||
header.alpha = ShadeInterpolation.getContentAlpha(value)
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expansion fraction of the QQS <-> QS animation.
|
||||
*/
|
||||
var qsExpandedFraction = -1f
|
||||
set(value) {
|
||||
if (visible && field != value) {
|
||||
field = value
|
||||
updatePosition()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current scroll of QS.
|
||||
*/
|
||||
var qsScrollY = 0
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
updateScrollY()
|
||||
}
|
||||
}
|
||||
|
||||
private val insetListener = View.OnApplyWindowInsetsListener { view, insets ->
|
||||
updateConstraintsForInsets(view as MotionLayout, insets)
|
||||
lastInsets = WindowInsets(insets)
|
||||
|
||||
view.onApplyWindowInsets(insets)
|
||||
}
|
||||
|
||||
private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
|
||||
override fun onChipVisibilityRefreshed(visible: Boolean) {
|
||||
if (header is MotionLayout) {
|
||||
// If the privacy chip is visible, we hide the status icons and battery remaining
|
||||
// icon, only in QQS.
|
||||
val update = combinedShadeHeadersConstraintManager
|
||||
.privacyChipVisibilityConstraints(visible)
|
||||
header.updateAllConstraints(update)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val configurationControllerListener =
|
||||
object : ConfigurationController.ConfigurationListener {
|
||||
override fun onConfigChanged(newConfig: Configuration?) {
|
||||
if (header !is MotionLayout) {
|
||||
val left = header.resources.getDimensionPixelSize(
|
||||
R.dimen.large_screen_shade_header_left_padding
|
||||
)
|
||||
header.setPadding(
|
||||
left,
|
||||
header.paddingTop,
|
||||
header.paddingRight,
|
||||
header.paddingBottom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDensityOrFontScaleChanged() {
|
||||
clock.setTextAppearance(R.style.TextAppearance_QS_Status)
|
||||
date.setTextAppearance(R.style.TextAppearance_QS_Status)
|
||||
qsCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
|
||||
if (header is MotionLayout) {
|
||||
loadConstraints()
|
||||
lastInsets?.let { updateConstraintsForInsets(header, it) }
|
||||
}
|
||||
updateResources()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInit() {
|
||||
if (header is MotionLayout) {
|
||||
variableDateViewControllerFactory.create(date as VariableDateView).init()
|
||||
}
|
||||
batteryMeterViewController.init()
|
||||
|
||||
// battery settings same as in QS icons
|
||||
batteryMeterViewController.ignoreTunerUpdates()
|
||||
batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
|
||||
|
||||
iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags)
|
||||
iconManager.setTint(
|
||||
Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
|
||||
)
|
||||
|
||||
carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
|
||||
listOf(
|
||||
header.context.getString(com.android.internal.R.string.status_bar_no_calling),
|
||||
header.context.getString(com.android.internal.R.string.status_bar_call_strength)
|
||||
)
|
||||
} else {
|
||||
listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
|
||||
}
|
||||
qsCarrierGroupController = qsCarrierGroupControllerBuilder
|
||||
.setQSCarrierGroup(qsCarrierGroup)
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun onViewAttached() {
|
||||
privacyIconsController.chipVisibilityListener = chipVisibilityListener
|
||||
if (header is MotionLayout) {
|
||||
header.setOnApplyWindowInsetsListener(insetListener)
|
||||
clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
|
||||
val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
|
||||
v.pivotX = newPivot
|
||||
}
|
||||
}
|
||||
|
||||
dumpManager.registerDumpable(this)
|
||||
configurationController.addCallback(configurationControllerListener)
|
||||
|
||||
updateVisibility()
|
||||
updateTransition()
|
||||
}
|
||||
|
||||
override fun onViewDetached() {
|
||||
privacyIconsController.chipVisibilityListener = null
|
||||
dumpManager.unregisterDumpable(this::class.java.simpleName)
|
||||
configurationController.removeCallback(configurationControllerListener)
|
||||
}
|
||||
|
||||
fun disable(state1: Int, state2: Int, animate: Boolean) {
|
||||
val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
|
||||
if (disabled == qsDisabled) return
|
||||
qsDisabled = disabled
|
||||
updateVisibility()
|
||||
}
|
||||
|
||||
private fun loadConstraints() {
|
||||
if (header is MotionLayout) {
|
||||
// Use resources.getXml instead of passing the resource id due to bug b/205018300
|
||||
header.getConstraintSet(QQS_HEADER_CONSTRAINT)
|
||||
.load(context, resources.getXml(R.xml.qqs_header))
|
||||
val qsConstraints = if (featureFlags.isEnabled(Flags.NEW_HEADER)) {
|
||||
R.xml.qs_header_new
|
||||
} else {
|
||||
R.xml.qs_header
|
||||
}
|
||||
header.getConstraintSet(QS_HEADER_CONSTRAINT)
|
||||
.load(context, resources.getXml(qsConstraints))
|
||||
header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
|
||||
.load(context, resources.getXml(R.xml.large_screen_shade_header))
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) {
|
||||
val cutout = insets.displayCutout
|
||||
|
||||
val sbInsets: Pair<Int, Int> = insetsProvider.getStatusBarContentInsetsForCurrentRotation()
|
||||
cutoutLeft = sbInsets.first
|
||||
cutoutRight = sbInsets.second
|
||||
val hasCornerCutout: Boolean = insetsProvider.currentRotationHasCornerCutout()
|
||||
updateQQSPaddings()
|
||||
// Set these guides as the left/right limits for content that lives in the top row, using
|
||||
// cutoutLeft and cutoutRight
|
||||
var changes = combinedShadeHeadersConstraintManager
|
||||
.edgesGuidelinesConstraints(
|
||||
if (view.isLayoutRtl) cutoutRight else cutoutLeft,
|
||||
header.paddingStart,
|
||||
if (view.isLayoutRtl) cutoutLeft else cutoutRight,
|
||||
header.paddingEnd
|
||||
)
|
||||
|
||||
if (cutout != null) {
|
||||
val topCutout = cutout.boundingRectTop
|
||||
if (topCutout.isEmpty || hasCornerCutout) {
|
||||
changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
|
||||
} else {
|
||||
changes += combinedShadeHeadersConstraintManager.centerCutoutConstraints(
|
||||
view.isLayoutRtl,
|
||||
(view.width - view.paddingLeft - view.paddingRight - topCutout.width()) / 2
|
||||
)
|
||||
}
|
||||
} else {
|
||||
changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
|
||||
}
|
||||
|
||||
view.updateAllConstraints(changes)
|
||||
}
|
||||
|
||||
private fun updateScrollY() {
|
||||
if (!largeScreenActive && combinedHeaders) {
|
||||
header.scrollY = qsScrollY
|
||||
}
|
||||
}
|
||||
|
||||
private fun onShadeExpandedChanged() {
|
||||
if (qsVisible) {
|
||||
privacyIconsController.startListening()
|
||||
} else {
|
||||
privacyIconsController.stopListening()
|
||||
}
|
||||
updateVisibility()
|
||||
updatePosition()
|
||||
}
|
||||
|
||||
private fun onHeaderStateChanged() {
|
||||
if (largeScreenActive || combinedHeaders) {
|
||||
privacyIconsController.onParentVisible()
|
||||
} else {
|
||||
privacyIconsController.onParentInvisible()
|
||||
}
|
||||
updateVisibility()
|
||||
updateTransition()
|
||||
}
|
||||
|
||||
/**
|
||||
* If not using [combinedHeaders] this should only be visible on large screen. Else, it should
|
||||
* be visible any time the QQS/QS shade is open.
|
||||
*/
|
||||
private fun updateVisibility() {
|
||||
val visibility = if (!largeScreenActive && !combinedHeaders || qsDisabled) {
|
||||
View.GONE
|
||||
} else if (qsVisible) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.INVISIBLE
|
||||
}
|
||||
if (header.visibility != visibility) {
|
||||
header.visibility = visibility
|
||||
visible = visibility == View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTransition() {
|
||||
if (!combinedHeaders) {
|
||||
return
|
||||
}
|
||||
header as MotionLayout
|
||||
if (largeScreenActive) {
|
||||
header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
|
||||
header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT).applyTo(header)
|
||||
} else {
|
||||
header.setTransition(HEADER_TRANSITION_ID)
|
||||
header.transitionToStart()
|
||||
updatePosition()
|
||||
updateScrollY()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePosition() {
|
||||
if (header is MotionLayout && !largeScreenActive && visible) {
|
||||
Trace.instantForTrack(
|
||||
TRACE_TAG_APP,
|
||||
"LargeScreenHeaderController - updatePosition",
|
||||
"position: $qsExpandedFraction"
|
||||
)
|
||||
header.progress = qsExpandedFraction
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateListeners() {
|
||||
qsCarrierGroupController.setListening(visible)
|
||||
if (visible) {
|
||||
updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
|
||||
qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
|
||||
statusBarIconController.addIconGroup(iconManager)
|
||||
} else {
|
||||
qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
|
||||
statusBarIconController.removeIconGroup(iconManager)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSingleCarrier(singleCarrier: Boolean) {
|
||||
if (singleCarrier) {
|
||||
iconContainer.removeIgnoredSlots(carrierIconSlots)
|
||||
} else {
|
||||
iconContainer.addIgnoredSlots(carrierIconSlots)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateResources() {
|
||||
roundedCorners = resources.getDimensionPixelSize(R.dimen.rounded_corner_content_padding)
|
||||
val padding = resources.getDimensionPixelSize(R.dimen.qs_panel_padding)
|
||||
header.setPadding(padding, header.paddingTop, padding, header.paddingBottom)
|
||||
updateQQSPaddings()
|
||||
}
|
||||
|
||||
private fun updateQQSPaddings() {
|
||||
if (header is MotionLayout) {
|
||||
val clockPaddingStart = resources
|
||||
.getDimensionPixelSize(R.dimen.status_bar_left_clock_starting_padding)
|
||||
val clockPaddingEnd = resources
|
||||
.getDimensionPixelSize(R.dimen.status_bar_left_clock_end_padding)
|
||||
clock.setPaddingRelative(
|
||||
clockPaddingStart,
|
||||
clock.paddingTop,
|
||||
clockPaddingEnd,
|
||||
clock.paddingBottom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
||||
pw.println("visible: $visible")
|
||||
pw.println("shadeExpanded: $qsVisible")
|
||||
pw.println("shadeExpandedFraction: $shadeExpandedFraction")
|
||||
pw.println("active: $largeScreenActive")
|
||||
pw.println("qsExpandedFraction: $qsExpandedFraction")
|
||||
pw.println("qsScrollY: $qsScrollY")
|
||||
if (combinedHeaders) {
|
||||
header as MotionLayout
|
||||
pw.println("currentState: ${header.currentState.stateToString()}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun MotionLayout.updateConstraints(@IdRes state: Int, update: ConstraintChange) {
|
||||
val constraints = getConstraintSet(state)
|
||||
constraints.update()
|
||||
updateState(state, constraints)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the [ConstraintSet] for the case of combined headers.
|
||||
*
|
||||
* Only non-`null` changes are applied to reduce the number of rebuilding in the [MotionLayout].
|
||||
*/
|
||||
private fun MotionLayout.updateAllConstraints(updates: ConstraintsChanges) {
|
||||
if (updates.qqsConstraintsChanges != null) {
|
||||
updateConstraints(QQS_HEADER_CONSTRAINT, updates.qqsConstraintsChanges)
|
||||
}
|
||||
if (updates.qsConstraintsChanges != null) {
|
||||
updateConstraints(QS_HEADER_CONSTRAINT, updates.qsConstraintsChanges)
|
||||
}
|
||||
if (updates.largeScreenConstraintsChanges != null) {
|
||||
updateConstraints(LARGE_SCREEN_HEADER_CONSTRAINT, updates.largeScreenConstraintsChanges)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,7 +189,6 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
|
||||
import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
|
||||
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
|
||||
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
|
||||
import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController;
|
||||
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
|
||||
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
|
||||
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
|
||||
@@ -1038,6 +1037,7 @@ public final class NotificationPanelViewController extends PanelViewController {
|
||||
}
|
||||
|
||||
mTapAgainViewController.init();
|
||||
mLargeScreenShadeHeaderController.init();
|
||||
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
|
||||
mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
|
||||
controller.setup(mNotificationContainerParent));
|
||||
@@ -1143,7 +1143,7 @@ public final class NotificationPanelViewController extends PanelViewController {
|
||||
SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
|
||||
int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
|
||||
mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
|
||||
mLargeScreenShadeHeaderController.setActive(mUseLargeScreenShadeHeader);
|
||||
mLargeScreenShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
|
||||
mAmbientState.setStackTopMargin(topMargin);
|
||||
mNotificationsQSContainerController.updateResources();
|
||||
|
||||
@@ -2417,7 +2417,7 @@ public final class NotificationPanelViewController extends PanelViewController {
|
||||
: getExpandedFraction();
|
||||
mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
|
||||
mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
|
||||
mLargeScreenShadeHeaderController.setShadeExpanded(mQsVisible);
|
||||
mLargeScreenShadeHeaderController.setQsVisible(mQsVisible);
|
||||
}
|
||||
|
||||
private void onStackYChanged(boolean shouldAnimate) {
|
||||
|
||||
@@ -1,311 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar.phone
|
||||
|
||||
import android.app.StatusBarManager
|
||||
import android.content.res.Configuration
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
import com.android.settingslib.Utils
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.FontSizeUtils
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.animation.ShadeInterpolation
|
||||
import com.android.systemui.battery.BatteryMeterView
|
||||
import com.android.systemui.battery.BatteryMeterViewController
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.qs.ChipVisibilityListener
|
||||
import com.android.systemui.qs.HeaderPrivacyIconsController
|
||||
import com.android.systemui.qs.carrier.QSCarrierGroup
|
||||
import com.android.systemui.qs.carrier.QSCarrierGroupController
|
||||
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
|
||||
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
|
||||
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import java.io.PrintWriter
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
@CentralSurfacesScope
|
||||
class LargeScreenShadeHeaderController @Inject constructor(
|
||||
@Named(LARGE_SCREEN_SHADE_HEADER) private val header: View,
|
||||
private val statusBarIconController: StatusBarIconController,
|
||||
private val privacyIconsController: HeaderPrivacyIconsController,
|
||||
private val configurationController: ConfigurationController,
|
||||
qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
|
||||
featureFlags: FeatureFlags,
|
||||
@Named(LARGE_SCREEN_BATTERY_CONTROLLER) batteryMeterViewController: BatteryMeterViewController,
|
||||
dumpManager: DumpManager
|
||||
) : Dumpable {
|
||||
|
||||
companion object {
|
||||
private val HEADER_TRANSITION_ID = R.id.header_transition
|
||||
private val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
|
||||
private val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
|
||||
private val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
|
||||
private val LARGE_SCREEN_HEADER_CONSTRAINT = R.id.large_screen_header_constraint
|
||||
|
||||
private fun Int.stateToString() = when (this) {
|
||||
QQS_HEADER_CONSTRAINT -> "QQS Header"
|
||||
QS_HEADER_CONSTRAINT -> "QS Header"
|
||||
LARGE_SCREEN_HEADER_CONSTRAINT -> "Large Screen Header"
|
||||
else -> "Unknown state"
|
||||
}
|
||||
}
|
||||
|
||||
private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
|
||||
private val iconManager: StatusBarIconController.TintedIconManager
|
||||
private val iconContainer: StatusIconContainer
|
||||
private val carrierIconSlots: List<String>
|
||||
private val qsCarrierGroupController: QSCarrierGroupController
|
||||
private val clock: TextView = header.findViewById(R.id.clock)
|
||||
private val date: TextView = header.findViewById(R.id.date)
|
||||
private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
|
||||
|
||||
private var qsDisabled = false
|
||||
|
||||
private var visible = false
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
field = value
|
||||
updateListeners()
|
||||
}
|
||||
|
||||
var shadeExpanded = false
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
field = value
|
||||
onShadeExpandedChanged()
|
||||
}
|
||||
|
||||
var active = false
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
field = value
|
||||
onHeaderStateChanged()
|
||||
}
|
||||
|
||||
var shadeExpandedFraction = -1f
|
||||
set(value) {
|
||||
if (visible && field != value) {
|
||||
header.alpha = ShadeInterpolation.getContentAlpha(value)
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
var qsExpandedFraction = -1f
|
||||
set(value) {
|
||||
if (visible && field != value) {
|
||||
field = value
|
||||
updateVisibility()
|
||||
updatePosition()
|
||||
}
|
||||
}
|
||||
|
||||
var qsScrollY = 0
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
updateScrollY()
|
||||
}
|
||||
}
|
||||
|
||||
private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
|
||||
override fun onChipVisibilityRefreshed(visible: Boolean) {
|
||||
if (header is MotionLayout) {
|
||||
val state = header.getConstraintSet(QQS_HEADER_CONSTRAINT).apply {
|
||||
setAlpha(R.id.statusIcons, if (visible) 0f else 1f)
|
||||
setAlpha(R.id.batteryRemainingIcon, if (visible) 0f else 1f)
|
||||
}
|
||||
header.updateState(QQS_HEADER_CONSTRAINT, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
if (header is MotionLayout) {
|
||||
val context = header.context
|
||||
val resources = header.resources
|
||||
header.getConstraintSet(QQS_HEADER_CONSTRAINT)
|
||||
.load(context, resources.getXml(R.xml.qqs_header))
|
||||
header.getConstraintSet(QS_HEADER_CONSTRAINT)
|
||||
.load(context, resources.getXml(R.xml.qs_header))
|
||||
header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
|
||||
.load(context, resources.getXml(R.xml.large_screen_shade_header))
|
||||
privacyIconsController.chipVisibilityListener = chipVisibilityListener
|
||||
}
|
||||
|
||||
bindConfigurationListener()
|
||||
|
||||
batteryMeterViewController.init()
|
||||
val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
|
||||
|
||||
// battery settings same as in QS icons
|
||||
batteryMeterViewController.ignoreTunerUpdates()
|
||||
batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
|
||||
|
||||
iconContainer = header.findViewById(R.id.statusIcons)
|
||||
iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags)
|
||||
iconManager.setTint(Utils.getColorAttrDefaultColor(header.context,
|
||||
android.R.attr.textColorPrimary))
|
||||
|
||||
carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
|
||||
listOf(
|
||||
header.context.getString(com.android.internal.R.string.status_bar_no_calling),
|
||||
header.context.getString(com.android.internal.R.string.status_bar_call_strength)
|
||||
)
|
||||
} else {
|
||||
listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
|
||||
}
|
||||
qsCarrierGroupController = qsCarrierGroupControllerBuilder
|
||||
.setQSCarrierGroup(header.findViewById(R.id.carrier_group))
|
||||
.build()
|
||||
|
||||
dumpManager.registerDumpable(this)
|
||||
|
||||
updateVisibility()
|
||||
updateConstraints()
|
||||
}
|
||||
|
||||
fun disable(state1: Int, state2: Int, animate: Boolean) {
|
||||
val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
|
||||
if (disabled == qsDisabled) return
|
||||
qsDisabled = disabled
|
||||
updateVisibility()
|
||||
}
|
||||
|
||||
private fun updateScrollY() {
|
||||
if (!active && combinedHeaders) {
|
||||
header.scrollY = qsScrollY
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindConfigurationListener() {
|
||||
val listener = object : ConfigurationController.ConfigurationListener {
|
||||
override fun onConfigChanged(newConfig: Configuration?) {
|
||||
val left = header.resources.getDimensionPixelSize(
|
||||
R.dimen.large_screen_shade_header_left_padding)
|
||||
header.setPadding(
|
||||
left, header.paddingTop, header.paddingRight, header.paddingBottom)
|
||||
}
|
||||
override fun onDensityOrFontScaleChanged() {
|
||||
val qsStatusStyle = R.style.TextAppearance_QS_Status
|
||||
FontSizeUtils.updateFontSizeFromStyle(clock, qsStatusStyle)
|
||||
FontSizeUtils.updateFontSizeFromStyle(date, qsStatusStyle)
|
||||
qsCarrierGroup.updateTextAppearance(qsStatusStyle)
|
||||
}
|
||||
}
|
||||
configurationController.addCallback(listener)
|
||||
}
|
||||
|
||||
private fun onShadeExpandedChanged() {
|
||||
if (shadeExpanded) {
|
||||
privacyIconsController.startListening()
|
||||
} else {
|
||||
privacyIconsController.stopListening()
|
||||
}
|
||||
updateVisibility()
|
||||
updatePosition()
|
||||
}
|
||||
|
||||
private fun onHeaderStateChanged() {
|
||||
if (active || combinedHeaders) {
|
||||
privacyIconsController.onParentVisible()
|
||||
} else {
|
||||
privacyIconsController.onParentInvisible()
|
||||
}
|
||||
updateVisibility()
|
||||
updateConstraints()
|
||||
}
|
||||
|
||||
private fun updateVisibility() {
|
||||
val visibility = if (!active && !combinedHeaders || qsDisabled) {
|
||||
View.GONE
|
||||
} else if (shadeExpanded) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.INVISIBLE
|
||||
}
|
||||
if (header.visibility != visibility) {
|
||||
header.visibility = visibility
|
||||
visible = visibility == View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateConstraints() {
|
||||
if (!combinedHeaders) {
|
||||
return
|
||||
}
|
||||
header as MotionLayout
|
||||
if (active) {
|
||||
header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
|
||||
} else {
|
||||
header.setTransition(HEADER_TRANSITION_ID)
|
||||
header.transitionToStart()
|
||||
updatePosition()
|
||||
updateScrollY()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePosition() {
|
||||
if (header is MotionLayout && !active && visible) {
|
||||
header.setProgress(qsExpandedFraction)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateListeners() {
|
||||
qsCarrierGroupController.setListening(visible)
|
||||
if (visible) {
|
||||
updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
|
||||
qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
|
||||
statusBarIconController.addIconGroup(iconManager)
|
||||
} else {
|
||||
qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
|
||||
statusBarIconController.removeIconGroup(iconManager)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSingleCarrier(singleCarrier: Boolean) {
|
||||
if (singleCarrier) {
|
||||
iconContainer.removeIgnoredSlots(carrierIconSlots)
|
||||
} else {
|
||||
iconContainer.addIgnoredSlots(carrierIconSlots)
|
||||
}
|
||||
}
|
||||
|
||||
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
||||
pw.println("visible: $visible")
|
||||
pw.println("shadeExpanded: $shadeExpanded")
|
||||
pw.println("shadeExpandedFraction: $shadeExpandedFraction")
|
||||
pw.println("active: $active")
|
||||
pw.println("qsExpandedFraction: $qsExpandedFraction")
|
||||
pw.println("qsScrollY: $qsScrollY")
|
||||
if (combinedHeaders) {
|
||||
header as MotionLayout
|
||||
pw.println("currentState: ${header.currentState.stateToString()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ class StatusBarContentInsetsProvider @Inject constructor(
|
||||
val point = Point()
|
||||
context.display.getRealSize(point)
|
||||
|
||||
return topBounds.left <= 0 || topBounds.right >= point.y
|
||||
return topBounds.left <= 0 || topBounds.right >= point.x
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import com.android.keyguard.LockIconViewController;
|
||||
import com.android.systemui.biometrics.AuthRippleController;
|
||||
import com.android.systemui.shade.LargeScreenShadeHeaderController;
|
||||
import com.android.systemui.shade.NotificationPanelViewController;
|
||||
import com.android.systemui.shade.NotificationShadeWindowView;
|
||||
import com.android.systemui.shade.NotificationShadeWindowViewController;
|
||||
@@ -35,7 +36,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutListContainerModule;
|
||||
import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks;
|
||||
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
|
||||
import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController;
|
||||
import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener;
|
||||
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarterModule;
|
||||
import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
|
||||
|
||||
@@ -35,6 +35,8 @@ import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.flags.Flags;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.privacy.OngoingPrivacyChip;
|
||||
import com.android.systemui.shade.CombinedShadeHeadersConstraintManager;
|
||||
import com.android.systemui.shade.CombinedShadeHeadersConstraintManagerImpl;
|
||||
import com.android.systemui.shade.NotificationPanelView;
|
||||
import com.android.systemui.shade.NotificationPanelViewController;
|
||||
import com.android.systemui.shade.NotificationShadeWindowView;
|
||||
@@ -176,6 +178,14 @@ public abstract class StatusBarViewModule {
|
||||
return v;
|
||||
}
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
@CentralSurfacesComponent.CentralSurfacesScope
|
||||
public static CombinedShadeHeadersConstraintManager
|
||||
provideCombinedShadeHeadersConstraintManager() {
|
||||
return CombinedShadeHeadersConstraintManagerImpl.INSTANCE;
|
||||
}
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
@CentralSurfacesComponent.CentralSurfacesScope
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.util
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.Choreographer
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
|
||||
/**
|
||||
* [MotionLayout] that avoids remeasuring with the same inputs in the same frame.
|
||||
*
|
||||
* This is important when this view is the child of a view that performs more than one measure pass
|
||||
* (e.g. [LinearLayout] or [ConstraintLayout]). In those cases, if this view is measured with the
|
||||
* same inputs in the same frame, we use the last result.
|
||||
*/
|
||||
class NoRemeasureMotionLayout @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet?,
|
||||
defStyle: Int = 0
|
||||
) : MotionLayout(context, attrs, defStyle) {
|
||||
|
||||
private var lastWidthSpec: Int? = null
|
||||
private var lastHeightSpec: Int? = null
|
||||
private var lastFrame: Long? = null
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
if (
|
||||
lastWidthSpec == widthMeasureSpec &&
|
||||
lastHeightSpec == heightMeasureSpec &&
|
||||
Choreographer.getMainThreadInstance()?.frameTime == lastFrame
|
||||
) {
|
||||
setMeasuredDimension(measuredWidth, measuredHeight)
|
||||
return
|
||||
}
|
||||
traceSection("NoRemeasureMotionLayout - measure") {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
lastWidthSpec = widthMeasureSpec
|
||||
lastHeightSpec = heightMeasureSpec
|
||||
lastFrame = Choreographer.getMainThreadInstance()?.frameTime
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.shade
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
|
||||
import androidx.constraintlayout.widget.ConstraintSet.START
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var qqsConstraint: ConstraintSet
|
||||
private lateinit var qsConstraint: ConstraintSet
|
||||
private lateinit var largeScreenConstraint: ConstraintSet
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
qqsConstraint = ConstraintSet().apply {
|
||||
load(context, context.resources.getXml(R.xml.qqs_header))
|
||||
}
|
||||
qsConstraint = ConstraintSet().apply {
|
||||
load(context, context.resources.getXml(R.xml.qs_header_new))
|
||||
}
|
||||
largeScreenConstraint = ConstraintSet().apply {
|
||||
load(context, context.resources.getXml(R.xml.large_screen_shade_header))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEdgeElementsAlignedWithGuide_qqs() {
|
||||
with(qqsConstraint) {
|
||||
assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(R.id.begin_guide)
|
||||
assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
|
||||
|
||||
assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
|
||||
.isEqualTo(R.id.end_guide)
|
||||
assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
|
||||
.isEqualTo(1f)
|
||||
|
||||
assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
|
||||
.isEqualTo(R.id.end_guide)
|
||||
assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias)
|
||||
.isEqualTo(1f)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClockScale() {
|
||||
with(qqsConstraint.getConstraint(R.id.clock)) {
|
||||
assertThat(transform.scaleX).isEqualTo(1f)
|
||||
assertThat(transform.scaleY).isEqualTo(1f)
|
||||
}
|
||||
with(qsConstraint.getConstraint(R.id.clock)) {
|
||||
assertThat(transform.scaleX).isGreaterThan(1f)
|
||||
assertThat(transform.scaleY).isGreaterThan(1f)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEdgeElementsAlignedWithEdgeOrGuide_qs() {
|
||||
with(qsConstraint) {
|
||||
assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID)
|
||||
assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
|
||||
|
||||
assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID)
|
||||
assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0f)
|
||||
|
||||
assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
|
||||
.isEqualTo(PARENT_ID)
|
||||
assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
|
||||
.isEqualTo(1f)
|
||||
|
||||
assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
|
||||
.isEqualTo(R.id.end_guide)
|
||||
assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(1f)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEdgeElementsAlignedWithEdge_largeScreen() {
|
||||
with(largeScreenConstraint) {
|
||||
assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID)
|
||||
assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
|
||||
|
||||
assertThat(getConstraint(R.id.privacy_container).layout.endToEnd).isEqualTo(PARENT_ID)
|
||||
assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(1f)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCarrierAlpha() {
|
||||
assertThat(qqsConstraint.getConstraint(R.id.carrier_group).propertySet.alpha).isEqualTo(0f)
|
||||
assertThat(qsConstraint.getConstraint(R.id.carrier_group).propertySet.alpha).isEqualTo(1f)
|
||||
assertThat(largeScreenConstraint.getConstraint(R.id.carrier_group).propertySet.alpha)
|
||||
.isEqualTo(1f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPrivacyChipVisibilityConstraints_notVisible() {
|
||||
val changes = CombinedShadeHeadersConstraintManagerImpl
|
||||
.privacyChipVisibilityConstraints(false)
|
||||
changes()
|
||||
|
||||
with(qqsConstraint) {
|
||||
assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
|
||||
assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
|
||||
}
|
||||
|
||||
with(qsConstraint) {
|
||||
assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
|
||||
assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
|
||||
}
|
||||
|
||||
with(largeScreenConstraint) {
|
||||
assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
|
||||
assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPrivacyChipVisibilityConstraints_visible() {
|
||||
val changes = CombinedShadeHeadersConstraintManagerImpl
|
||||
.privacyChipVisibilityConstraints(true)
|
||||
changes()
|
||||
|
||||
with(qqsConstraint) {
|
||||
assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(0f)
|
||||
assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(0f)
|
||||
}
|
||||
|
||||
with(qsConstraint) {
|
||||
assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
|
||||
assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
|
||||
}
|
||||
|
||||
with(largeScreenConstraint) {
|
||||
assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
|
||||
assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEmptyCutoutConstraints() {
|
||||
val changes = CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()
|
||||
changes()
|
||||
|
||||
// QS and Large Screen don't change with cutouts.
|
||||
assertThat(changes.qsConstraintsChanges).isNull()
|
||||
assertThat(changes.largeScreenConstraintsChanges).isNull()
|
||||
|
||||
with(qqsConstraint) {
|
||||
// In this case, the date is constrained on the end by a Barrier determined by either
|
||||
// privacy or statusIcons
|
||||
assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.barrier)
|
||||
assertThat(getConstraint(R.id.statusIcons).layout.startToEnd).isEqualTo(R.id.date)
|
||||
assertThat(getConstraint(R.id.privacy_container).layout.startToEnd).isEqualTo(R.id.date)
|
||||
assertThat(getConstraint(R.id.barrier).layout.mReferenceIds).asList().containsExactly(
|
||||
R.id.statusIcons,
|
||||
R.id.privacy_container
|
||||
)
|
||||
assertThat(getConstraint(R.id.barrier).layout.mBarrierDirection).isEqualTo(START)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGuidesAreSetInCorrectPosition_largeCutoutSmallerPadding() {
|
||||
val cutoutStart = 100
|
||||
val padding = 10
|
||||
val cutoutEnd = 30
|
||||
val changes = CombinedShadeHeadersConstraintManagerImpl.edgesGuidelinesConstraints(
|
||||
cutoutStart,
|
||||
padding,
|
||||
cutoutEnd,
|
||||
padding
|
||||
)
|
||||
changes()
|
||||
|
||||
with(qqsConstraint) {
|
||||
assertThat(getConstraint(R.id.begin_guide).layout.guideBegin)
|
||||
.isEqualTo(cutoutStart - padding)
|
||||
assertThat(getConstraint(R.id.end_guide).layout.guideEnd)
|
||||
.isEqualTo(cutoutEnd - padding)
|
||||
}
|
||||
|
||||
with(qsConstraint) {
|
||||
assertThat(getConstraint(R.id.begin_guide).layout.guideBegin)
|
||||
.isEqualTo(cutoutStart - padding)
|
||||
assertThat(getConstraint(R.id.end_guide).layout.guideEnd)
|
||||
.isEqualTo(cutoutEnd - padding)
|
||||
}
|
||||
|
||||
assertThat(changes.largeScreenConstraintsChanges).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGuidesAreSetInCorrectPosition_smallCutoutLargerPadding() {
|
||||
val cutoutStart = 5
|
||||
val padding = 10
|
||||
val cutoutEnd = 10
|
||||
|
||||
val changes = CombinedShadeHeadersConstraintManagerImpl.edgesGuidelinesConstraints(
|
||||
cutoutStart,
|
||||
padding,
|
||||
cutoutEnd,
|
||||
padding
|
||||
)
|
||||
changes()
|
||||
|
||||
with(qqsConstraint) {
|
||||
assertThat(getConstraint(R.id.begin_guide).layout.guideBegin).isEqualTo(0)
|
||||
assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
|
||||
}
|
||||
|
||||
with(qsConstraint) {
|
||||
assertThat(getConstraint(R.id.begin_guide).layout.guideBegin).isEqualTo(0)
|
||||
assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
|
||||
}
|
||||
|
||||
assertThat(changes.largeScreenConstraintsChanges).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCenterCutoutConstraints_ltr() {
|
||||
val offsetFromEdge = 400
|
||||
val rtl = false
|
||||
|
||||
val changes = CombinedShadeHeadersConstraintManagerImpl
|
||||
.centerCutoutConstraints(rtl, offsetFromEdge)
|
||||
changes()
|
||||
|
||||
// In LTR, center_left is towards the start and center_right is towards the end
|
||||
with(qqsConstraint) {
|
||||
assertThat(getConstraint(R.id.center_left).layout.guideBegin).isEqualTo(offsetFromEdge)
|
||||
assertThat(getConstraint(R.id.center_right).layout.guideEnd).isEqualTo(offsetFromEdge)
|
||||
assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.center_left)
|
||||
assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
|
||||
.isEqualTo(R.id.center_right)
|
||||
assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
|
||||
.isEqualTo(R.id.center_right)
|
||||
}
|
||||
|
||||
with(qsConstraint) {
|
||||
assertThat(getConstraint(R.id.center_left).layout.guideBegin).isEqualTo(offsetFromEdge)
|
||||
assertThat(getConstraint(R.id.center_right).layout.guideEnd).isEqualTo(offsetFromEdge)
|
||||
|
||||
assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_left)
|
||||
assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_right)
|
||||
|
||||
assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
|
||||
.isNotEqualTo(R.id.center_left)
|
||||
assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
|
||||
.isNotEqualTo(R.id.center_right)
|
||||
|
||||
assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
|
||||
.isEqualTo(R.id.center_right)
|
||||
}
|
||||
|
||||
assertThat(changes.largeScreenConstraintsChanges).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCenterCutoutConstraints_rtl() {
|
||||
val offsetFromEdge = 400
|
||||
val rtl = true
|
||||
|
||||
val changes = CombinedShadeHeadersConstraintManagerImpl
|
||||
.centerCutoutConstraints(rtl, offsetFromEdge)
|
||||
changes()
|
||||
|
||||
// In RTL, center_left is towards the end and center_right is towards the start
|
||||
with(qqsConstraint) {
|
||||
assertThat(getConstraint(R.id.center_left).layout.guideEnd).isEqualTo(offsetFromEdge)
|
||||
assertThat(getConstraint(R.id.center_right).layout.guideBegin).isEqualTo(offsetFromEdge)
|
||||
assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.center_right)
|
||||
assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
|
||||
.isEqualTo(R.id.center_left)
|
||||
assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
|
||||
.isEqualTo(R.id.center_left)
|
||||
}
|
||||
|
||||
with(qsConstraint) {
|
||||
assertThat(getConstraint(R.id.center_left).layout.guideEnd).isEqualTo(offsetFromEdge)
|
||||
assertThat(getConstraint(R.id.center_right).layout.guideBegin).isEqualTo(offsetFromEdge)
|
||||
|
||||
assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_left)
|
||||
assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_right)
|
||||
|
||||
assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
|
||||
.isNotEqualTo(R.id.center_left)
|
||||
assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
|
||||
.isNotEqualTo(R.id.center_right)
|
||||
|
||||
assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
|
||||
.isEqualTo(R.id.center_left)
|
||||
}
|
||||
|
||||
assertThat(changes.largeScreenConstraintsChanges).isNull()
|
||||
}
|
||||
|
||||
private operator fun ConstraintsChanges.invoke() {
|
||||
qqsConstraintsChanges?.invoke(qqsConstraint)
|
||||
qsConstraintsChanges?.invoke(qsConstraint)
|
||||
largeScreenConstraintsChanges?.invoke(largeScreenConstraint)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.systemui.shade
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mockito.inOrder
|
||||
import org.mockito.Mockito.verify
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class ConstraintChangeTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testSumNonNull() {
|
||||
val mock1: ConstraintChange = mock()
|
||||
val mock2: ConstraintChange = mock()
|
||||
|
||||
val constraintSet = ConstraintSet()
|
||||
|
||||
val sum = mock1 + mock2
|
||||
sum?.invoke(constraintSet)
|
||||
|
||||
val inOrder = inOrder(mock1, mock2)
|
||||
inOrder.verify(mock1).invoke(constraintSet)
|
||||
inOrder.verify(mock2).invoke(constraintSet)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSumThisNull() {
|
||||
val mock: ConstraintChange = mock()
|
||||
val constraintSet = ConstraintSet()
|
||||
|
||||
val sum = (null as? ConstraintChange?) + mock
|
||||
sum?.invoke(constraintSet)
|
||||
|
||||
verify(mock).invoke(constraintSet)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSumThisNull_notWrapped() {
|
||||
val change: ConstraintChange = {}
|
||||
|
||||
val sum = (null as? ConstraintChange?) + change
|
||||
assertThat(sum).isSameInstanceAs(change)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSumOtherNull() {
|
||||
val mock: ConstraintChange = mock()
|
||||
val constraintSet = ConstraintSet()
|
||||
|
||||
val sum = mock + (null as? ConstraintChange?)
|
||||
sum?.invoke(constraintSet)
|
||||
|
||||
verify(mock).invoke(constraintSet)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSumOtherNull_notWrapped() {
|
||||
val change: ConstraintChange = {}
|
||||
|
||||
val sum = change + (null as? ConstraintChange?)
|
||||
assertThat(sum).isSameInstanceAs(change)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.systemui.shade
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mockito.inOrder
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class ConstraintChangesTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testSumWithoutNulls() {
|
||||
val mockQQS1: ConstraintChange = mock()
|
||||
val mockQS1: ConstraintChange = mock()
|
||||
val mockLS1: ConstraintChange = mock()
|
||||
val mockQQS2: ConstraintChange = mock()
|
||||
val mockQS2: ConstraintChange = mock()
|
||||
val mockLS2: ConstraintChange = mock()
|
||||
|
||||
val changes1 = ConstraintsChanges(mockQQS1, mockQS1, mockLS1)
|
||||
val changes2 = ConstraintsChanges(mockQQS2, mockQS2, mockLS2)
|
||||
|
||||
val sum = changes1 + changes2
|
||||
|
||||
val constraintSet = ConstraintSet()
|
||||
sum.qqsConstraintsChanges?.invoke(constraintSet)
|
||||
sum.qsConstraintsChanges?.invoke(constraintSet)
|
||||
sum.largeScreenConstraintsChanges?.invoke(constraintSet)
|
||||
|
||||
val inOrder = inOrder(mockQQS1, mockQS1, mockLS1, mockQQS2, mockQS2, mockLS2)
|
||||
|
||||
inOrder.verify(mockQQS1).invoke(constraintSet)
|
||||
inOrder.verify(mockQQS2).invoke(constraintSet)
|
||||
inOrder.verify(mockQS1).invoke(constraintSet)
|
||||
inOrder.verify(mockQS2).invoke(constraintSet)
|
||||
inOrder.verify(mockLS1).invoke(constraintSet)
|
||||
inOrder.verify(mockLS2).invoke(constraintSet)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSumWithSomeNulls() {
|
||||
val mockQQS: ConstraintChange = mock()
|
||||
val mockQS: ConstraintChange = mock()
|
||||
|
||||
val changes1 = ConstraintsChanges(mockQQS, null, null)
|
||||
val changes2 = ConstraintsChanges(null, mockQS, null)
|
||||
|
||||
val sum = changes1 + changes2
|
||||
|
||||
assertThat(sum.qqsConstraintsChanges).isSameInstanceAs(mockQQS)
|
||||
assertThat(sum.qsConstraintsChanges).isSameInstanceAs(mockQS)
|
||||
assertThat(sum.largeScreenConstraintsChanges).isNull()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,659 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.shade
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.content.res.XmlResourceParser
|
||||
import android.graphics.Rect
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.view.DisplayCutout
|
||||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.animation.ShadeInterpolation
|
||||
import com.android.systemui.battery.BatteryMeterView
|
||||
import com.android.systemui.battery.BatteryMeterViewController
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.qs.ChipVisibilityListener
|
||||
import com.android.systemui.qs.HeaderPrivacyIconsController
|
||||
import com.android.systemui.qs.carrier.QSCarrierGroup
|
||||
import com.android.systemui.qs.carrier.QSCarrierGroupController
|
||||
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
|
||||
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
|
||||
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
|
||||
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
|
||||
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
|
||||
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController
|
||||
import com.android.systemui.statusbar.phone.StatusIconContainer
|
||||
import com.android.systemui.statusbar.policy.FakeConfigurationController
|
||||
import com.android.systemui.statusbar.policy.VariableDateView
|
||||
import com.android.systemui.statusbar.policy.VariableDateViewController
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Answers
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.anyBoolean
|
||||
import org.mockito.Mockito.anyFloat
|
||||
import org.mockito.Mockito.anyInt
|
||||
import org.mockito.Mockito.clearInvocations
|
||||
import org.mockito.Mockito.inOrder
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
|
||||
private val EMPTY_CHANGES = ConstraintsChanges()
|
||||
|
||||
/**
|
||||
* Tests for [LargeScreenShadeHeaderController] when [Flags.COMBINED_QS_HEADERS] is `true`.
|
||||
*
|
||||
* Once that flag is removed, this class will be combined with
|
||||
* [LargeScreenShadeHeaderControllerTest].
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
|
||||
@Mock
|
||||
private lateinit var statusIcons: StatusIconContainer
|
||||
@Mock
|
||||
private lateinit var statusBarIconController: StatusBarIconController
|
||||
@Mock
|
||||
private lateinit var qsCarrierGroupController: QSCarrierGroupController
|
||||
@Mock
|
||||
private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
|
||||
@Mock
|
||||
private lateinit var featureFlags: FeatureFlags
|
||||
@Mock
|
||||
private lateinit var clock: TextView
|
||||
@Mock
|
||||
private lateinit var date: VariableDateView
|
||||
@Mock
|
||||
private lateinit var carrierGroup: QSCarrierGroup
|
||||
@Mock
|
||||
private lateinit var batteryMeterView: BatteryMeterView
|
||||
@Mock
|
||||
private lateinit var batteryMeterViewController: BatteryMeterViewController
|
||||
@Mock
|
||||
private lateinit var privacyIconsController: HeaderPrivacyIconsController
|
||||
@Mock
|
||||
private lateinit var insetsProvider: StatusBarContentInsetsProvider
|
||||
@Mock
|
||||
private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
|
||||
@Mock
|
||||
private lateinit var variableDateViewController: VariableDateViewController
|
||||
@Mock
|
||||
private lateinit var dumpManager: DumpManager
|
||||
@Mock
|
||||
private lateinit var combinedShadeHeadersConstraintManager:
|
||||
CombinedShadeHeadersConstraintManager
|
||||
|
||||
@Mock
|
||||
private lateinit var mockedContext: Context
|
||||
@Mock(answer = Answers.RETURNS_MOCKS)
|
||||
private lateinit var view: MotionLayout
|
||||
|
||||
@Mock
|
||||
private lateinit var qqsConstraints: ConstraintSet
|
||||
@Mock
|
||||
private lateinit var qsConstraints: ConstraintSet
|
||||
@Mock
|
||||
private lateinit var largeScreenConstraints: ConstraintSet
|
||||
|
||||
@JvmField @Rule
|
||||
val mockitoRule = MockitoJUnit.rule()
|
||||
var viewVisibility = View.GONE
|
||||
|
||||
private lateinit var controller: LargeScreenShadeHeaderController
|
||||
private lateinit var carrierIconSlots: List<String>
|
||||
private val configurationController = FakeConfigurationController()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
|
||||
whenever(clock.context).thenReturn(mockedContext)
|
||||
|
||||
whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
|
||||
whenever(date.context).thenReturn(mockedContext)
|
||||
whenever(variableDateViewControllerFactory.create(any()))
|
||||
.thenReturn(variableDateViewController)
|
||||
|
||||
whenever<QSCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
|
||||
whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon))
|
||||
.thenReturn(batteryMeterView)
|
||||
|
||||
whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
|
||||
whenever(statusIcons.context).thenReturn(context)
|
||||
|
||||
whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any()))
|
||||
.thenReturn(qsCarrierGroupControllerBuilder)
|
||||
whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
|
||||
|
||||
whenever(view.context).thenReturn(context)
|
||||
whenever(view.resources).thenReturn(context.resources)
|
||||
whenever(view.setVisibility(ArgumentMatchers.anyInt())).then {
|
||||
viewVisibility = it.arguments[0] as Int
|
||||
null
|
||||
}
|
||||
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
|
||||
|
||||
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
|
||||
whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
|
||||
|
||||
setUpDefaultInsets()
|
||||
setUpMotionLayout(view)
|
||||
|
||||
controller = LargeScreenShadeHeaderController(
|
||||
view,
|
||||
statusBarIconController,
|
||||
privacyIconsController,
|
||||
insetsProvider,
|
||||
configurationController,
|
||||
variableDateViewControllerFactory,
|
||||
batteryMeterViewController,
|
||||
dumpManager,
|
||||
featureFlags,
|
||||
qsCarrierGroupControllerBuilder,
|
||||
combinedShadeHeadersConstraintManager
|
||||
)
|
||||
whenever(view.isAttachedToWindow).thenReturn(true)
|
||||
controller.init()
|
||||
carrierIconSlots = listOf(
|
||||
context.getString(com.android.internal.R.string.status_bar_mobile))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCorrectConstraints() {
|
||||
val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java)
|
||||
|
||||
verify(qqsConstraints).load(eq(context), capture(captor))
|
||||
assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
|
||||
|
||||
verify(qsConstraints).load(eq(context), capture(captor))
|
||||
assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header_new)
|
||||
|
||||
verify(largeScreenConstraints).load(eq(context), capture(captor))
|
||||
assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testControllersCreatedAndInitialized() {
|
||||
verify(variableDateViewController).init()
|
||||
|
||||
verify(batteryMeterViewController).init()
|
||||
verify(batteryMeterViewController).ignoreTunerUpdates()
|
||||
verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
|
||||
|
||||
val inOrder = inOrder(qsCarrierGroupControllerBuilder)
|
||||
inOrder.verify(qsCarrierGroupControllerBuilder).setQSCarrierGroup(carrierGroup)
|
||||
inOrder.verify(qsCarrierGroupControllerBuilder).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClockPivotLtr() {
|
||||
val width = 200
|
||||
whenever(clock.width).thenReturn(width)
|
||||
whenever(clock.isLayoutRtl).thenReturn(false)
|
||||
|
||||
val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
|
||||
verify(clock).addOnLayoutChangeListener(capture(captor))
|
||||
|
||||
captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
|
||||
verify(clock).pivotX = 0f
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClockPivotRtl() {
|
||||
val width = 200
|
||||
whenever(clock.width).thenReturn(width)
|
||||
whenever(clock.isLayoutRtl).thenReturn(true)
|
||||
|
||||
val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
|
||||
verify(clock).addOnLayoutChangeListener(capture(captor))
|
||||
|
||||
captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
|
||||
verify(clock).pivotX = width.toFloat()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShadeExpanded_true() {
|
||||
// When shade is expanded, view should be visible regardless of largeScreenActive
|
||||
controller.largeScreenActive = false
|
||||
controller.qsVisible = true
|
||||
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
|
||||
|
||||
controller.largeScreenActive = true
|
||||
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShadeExpanded_false() {
|
||||
// When shade is not expanded, view should be invisible regardless of largeScreenActive
|
||||
controller.largeScreenActive = false
|
||||
controller.qsVisible = false
|
||||
assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
|
||||
|
||||
controller.largeScreenActive = true
|
||||
assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLargeScreenActive_true() {
|
||||
controller.largeScreenActive = false // Make sure there's a change
|
||||
clearInvocations(view)
|
||||
|
||||
controller.largeScreenActive = true
|
||||
|
||||
verify(view).setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLargeScreenActive_false() {
|
||||
controller.largeScreenActive = true // Make sure there's a change
|
||||
clearInvocations(view)
|
||||
|
||||
controller.largeScreenActive = false
|
||||
|
||||
verify(view).setTransition(HEADER_TRANSITION_ID)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShadeExpandedFraction() {
|
||||
// View needs to be visible for this to actually take effect
|
||||
controller.qsVisible = true
|
||||
|
||||
clearInvocations(view)
|
||||
controller.shadeExpandedFraction = 0.3f
|
||||
verify(view).alpha = ShadeInterpolation.getContentAlpha(0.3f)
|
||||
|
||||
clearInvocations(view)
|
||||
controller.shadeExpandedFraction = 1f
|
||||
verify(view).alpha = ShadeInterpolation.getContentAlpha(1f)
|
||||
|
||||
clearInvocations(view)
|
||||
controller.shadeExpandedFraction = 0f
|
||||
verify(view).alpha = ShadeInterpolation.getContentAlpha(0f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testQsExpandedFraction_headerTransition() {
|
||||
controller.qsVisible = true
|
||||
controller.largeScreenActive = false
|
||||
|
||||
clearInvocations(view)
|
||||
controller.qsExpandedFraction = 0.3f
|
||||
verify(view).progress = 0.3f
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testQsExpandedFraction_largeScreen() {
|
||||
controller.qsVisible = true
|
||||
controller.largeScreenActive = true
|
||||
|
||||
clearInvocations(view)
|
||||
controller.qsExpandedFraction = 0.3f
|
||||
verify(view, never()).progress = anyFloat()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScrollY_headerTransition() {
|
||||
controller.largeScreenActive = false
|
||||
|
||||
clearInvocations(view)
|
||||
controller.qsScrollY = 20
|
||||
verify(view).scrollY = 20
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScrollY_largeScreen() {
|
||||
controller.largeScreenActive = true
|
||||
|
||||
clearInvocations(view)
|
||||
controller.qsScrollY = 20
|
||||
verify(view, never()).scrollY = anyInt()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPrivacyChipVisibilityChanged_visible_changesCorrectConstraints() {
|
||||
val chipVisibleChanges = createMockConstraintChanges()
|
||||
val chipNotVisibleChanges = createMockConstraintChanges()
|
||||
|
||||
whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(true))
|
||||
.thenReturn(chipVisibleChanges)
|
||||
whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(false))
|
||||
.thenReturn(chipNotVisibleChanges)
|
||||
|
||||
val captor = ArgumentCaptor.forClass(ChipVisibilityListener::class.java)
|
||||
verify(privacyIconsController).chipVisibilityListener = capture(captor)
|
||||
|
||||
captor.value.onChipVisibilityRefreshed(true)
|
||||
|
||||
verify(chipVisibleChanges.qqsConstraintsChanges)!!.invoke(qqsConstraints)
|
||||
verify(chipVisibleChanges.qsConstraintsChanges)!!.invoke(qsConstraints)
|
||||
verify(chipVisibleChanges.largeScreenConstraintsChanges)!!.invoke(largeScreenConstraints)
|
||||
|
||||
verify(chipNotVisibleChanges.qqsConstraintsChanges, never())!!.invoke(any())
|
||||
verify(chipNotVisibleChanges.qsConstraintsChanges, never())!!.invoke(any())
|
||||
verify(chipNotVisibleChanges.largeScreenConstraintsChanges, never())!!.invoke(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPrivacyChipVisibilityChanged_notVisible_changesCorrectConstraints() {
|
||||
val chipVisibleChanges = createMockConstraintChanges()
|
||||
val chipNotVisibleChanges = createMockConstraintChanges()
|
||||
|
||||
whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(true))
|
||||
.thenReturn(chipVisibleChanges)
|
||||
whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(false))
|
||||
.thenReturn(chipNotVisibleChanges)
|
||||
|
||||
val captor = ArgumentCaptor.forClass(ChipVisibilityListener::class.java)
|
||||
verify(privacyIconsController).chipVisibilityListener = capture(captor)
|
||||
|
||||
captor.value.onChipVisibilityRefreshed(false)
|
||||
|
||||
verify(chipVisibleChanges.qqsConstraintsChanges, never())!!.invoke(qqsConstraints)
|
||||
verify(chipVisibleChanges.qsConstraintsChanges, never())!!.invoke(qsConstraints)
|
||||
verify(chipVisibleChanges.largeScreenConstraintsChanges, never())!!
|
||||
.invoke(largeScreenConstraints)
|
||||
|
||||
verify(chipNotVisibleChanges.qqsConstraintsChanges)!!.invoke(any())
|
||||
verify(chipNotVisibleChanges.qsConstraintsChanges)!!.invoke(any())
|
||||
verify(chipNotVisibleChanges.largeScreenConstraintsChanges)!!.invoke(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInsetsGuides_ltr() {
|
||||
whenever(view.isLayoutRtl).thenReturn(false)
|
||||
val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
|
||||
verify(view).setOnApplyWindowInsetsListener(capture(captor))
|
||||
val mockConstraintsChanges = createMockConstraintChanges()
|
||||
|
||||
val (insetLeft, insetRight) = 30 to 40
|
||||
val (paddingStart, paddingEnd) = 10 to 20
|
||||
whenever(view.paddingStart).thenReturn(paddingStart)
|
||||
whenever(view.paddingEnd).thenReturn(paddingEnd)
|
||||
|
||||
mockInsetsProvider(insetLeft to insetRight, false)
|
||||
|
||||
whenever(combinedShadeHeadersConstraintManager
|
||||
.edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
|
||||
).thenReturn(mockConstraintsChanges)
|
||||
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets())
|
||||
|
||||
verify(combinedShadeHeadersConstraintManager)
|
||||
.edgesGuidelinesConstraints(insetLeft, paddingStart, insetRight, paddingEnd)
|
||||
|
||||
verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInsetsGuides_rtl() {
|
||||
whenever(view.isLayoutRtl).thenReturn(true)
|
||||
val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
|
||||
verify(view).setOnApplyWindowInsetsListener(capture(captor))
|
||||
val mockConstraintsChanges = createMockConstraintChanges()
|
||||
|
||||
val (insetLeft, insetRight) = 30 to 40
|
||||
val (paddingStart, paddingEnd) = 10 to 20
|
||||
whenever(view.paddingStart).thenReturn(paddingStart)
|
||||
whenever(view.paddingEnd).thenReturn(paddingEnd)
|
||||
|
||||
mockInsetsProvider(insetLeft to insetRight, false)
|
||||
|
||||
whenever(combinedShadeHeadersConstraintManager
|
||||
.edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
|
||||
).thenReturn(mockConstraintsChanges)
|
||||
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets())
|
||||
|
||||
verify(combinedShadeHeadersConstraintManager)
|
||||
.edgesGuidelinesConstraints(insetRight, paddingStart, insetLeft, paddingEnd)
|
||||
|
||||
verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNullCutout() {
|
||||
val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
|
||||
verify(view).setOnApplyWindowInsetsListener(capture(captor))
|
||||
val mockConstraintsChanges = createMockConstraintChanges()
|
||||
|
||||
whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
|
||||
.thenReturn(mockConstraintsChanges)
|
||||
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets(null))
|
||||
|
||||
verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
|
||||
verify(combinedShadeHeadersConstraintManager, never())
|
||||
.centerCutoutConstraints(anyBoolean(), anyInt())
|
||||
|
||||
verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEmptyCutout() {
|
||||
val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
|
||||
verify(view).setOnApplyWindowInsetsListener(capture(captor))
|
||||
val mockConstraintsChanges = createMockConstraintChanges()
|
||||
|
||||
whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
|
||||
.thenReturn(mockConstraintsChanges)
|
||||
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets())
|
||||
|
||||
verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
|
||||
verify(combinedShadeHeadersConstraintManager, never())
|
||||
.centerCutoutConstraints(anyBoolean(), anyInt())
|
||||
|
||||
verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCornerCutout_emptyRect() {
|
||||
val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
|
||||
verify(view).setOnApplyWindowInsetsListener(capture(captor))
|
||||
val mockConstraintsChanges = createMockConstraintChanges()
|
||||
|
||||
mockInsetsProvider(0 to 0, true)
|
||||
|
||||
whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
|
||||
.thenReturn(mockConstraintsChanges)
|
||||
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets())
|
||||
|
||||
verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
|
||||
verify(combinedShadeHeadersConstraintManager, never())
|
||||
.centerCutoutConstraints(anyBoolean(), anyInt())
|
||||
|
||||
verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCornerCutout_nonEmptyRect() {
|
||||
val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
|
||||
verify(view).setOnApplyWindowInsetsListener(capture(captor))
|
||||
val mockConstraintsChanges = createMockConstraintChanges()
|
||||
|
||||
mockInsetsProvider(0 to 0, true)
|
||||
|
||||
whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
|
||||
.thenReturn(mockConstraintsChanges)
|
||||
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(1, 2, 3, 4)))
|
||||
|
||||
verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
|
||||
verify(combinedShadeHeadersConstraintManager, never())
|
||||
.centerCutoutConstraints(anyBoolean(), anyInt())
|
||||
|
||||
verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTopCutout_ltr() {
|
||||
val width = 100
|
||||
val paddingLeft = 10
|
||||
val paddingRight = 20
|
||||
val cutoutWidth = 30
|
||||
|
||||
whenever(view.isLayoutRtl).thenReturn(false)
|
||||
whenever(view.width).thenReturn(width)
|
||||
whenever(view.paddingLeft).thenReturn(paddingLeft)
|
||||
whenever(view.paddingRight).thenReturn(paddingRight)
|
||||
|
||||
val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
|
||||
verify(view).setOnApplyWindowInsetsListener(capture(captor))
|
||||
val mockConstraintsChanges = createMockConstraintChanges()
|
||||
|
||||
mockInsetsProvider(0 to 0, false)
|
||||
|
||||
whenever(combinedShadeHeadersConstraintManager
|
||||
.centerCutoutConstraints(anyBoolean(), anyInt())
|
||||
).thenReturn(mockConstraintsChanges)
|
||||
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(0, 0, cutoutWidth, 1)))
|
||||
|
||||
verify(combinedShadeHeadersConstraintManager, never()).emptyCutoutConstraints()
|
||||
val offset = (width - paddingLeft - paddingRight - cutoutWidth) / 2
|
||||
verify(combinedShadeHeadersConstraintManager).centerCutoutConstraints(false, offset)
|
||||
|
||||
verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTopCutout_rtl() {
|
||||
val width = 100
|
||||
val paddingLeft = 10
|
||||
val paddingRight = 20
|
||||
val cutoutWidth = 30
|
||||
|
||||
whenever(view.isLayoutRtl).thenReturn(true)
|
||||
whenever(view.width).thenReturn(width)
|
||||
whenever(view.paddingLeft).thenReturn(paddingLeft)
|
||||
whenever(view.paddingRight).thenReturn(paddingRight)
|
||||
|
||||
val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
|
||||
verify(view).setOnApplyWindowInsetsListener(capture(captor))
|
||||
val mockConstraintsChanges = createMockConstraintChanges()
|
||||
|
||||
mockInsetsProvider(0 to 0, false)
|
||||
|
||||
whenever(combinedShadeHeadersConstraintManager
|
||||
.centerCutoutConstraints(anyBoolean(), anyInt())
|
||||
).thenReturn(mockConstraintsChanges)
|
||||
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(0, 0, cutoutWidth, 1)))
|
||||
|
||||
verify(combinedShadeHeadersConstraintManager, never()).emptyCutoutConstraints()
|
||||
val offset = (width - paddingLeft - paddingRight - cutoutWidth) / 2
|
||||
verify(combinedShadeHeadersConstraintManager).centerCutoutConstraints(true, offset)
|
||||
|
||||
verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
|
||||
}
|
||||
|
||||
private fun createWindowInsets(
|
||||
topCutout: Rect? = Rect()
|
||||
): WindowInsets {
|
||||
val windowInsets: WindowInsets = mock()
|
||||
val displayCutout: DisplayCutout = mock()
|
||||
whenever(windowInsets.displayCutout)
|
||||
.thenReturn(if (topCutout != null) displayCutout else null)
|
||||
whenever(displayCutout.boundingRectTop).thenReturn(topCutout)
|
||||
|
||||
return windowInsets
|
||||
}
|
||||
|
||||
private fun mockInsetsProvider(
|
||||
insets: Pair<Int, Int> = 0 to 0,
|
||||
cornerCutout: Boolean = false,
|
||||
) {
|
||||
whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
|
||||
.thenReturn(insets.toAndroidPair())
|
||||
whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(cornerCutout)
|
||||
}
|
||||
|
||||
private fun createMockConstraintChanges(): ConstraintsChanges {
|
||||
return ConstraintsChanges(mock(), mock(), mock())
|
||||
}
|
||||
|
||||
private fun XmlResourceParser.getResId(): Int {
|
||||
return Resources.getAttributeSetSourceResId(this)
|
||||
}
|
||||
|
||||
private fun setUpMotionLayout(motionLayout: MotionLayout) {
|
||||
whenever(motionLayout.getConstraintSet(QQS_HEADER_CONSTRAINT)).thenReturn(qqsConstraints)
|
||||
whenever(motionLayout.getConstraintSet(QS_HEADER_CONSTRAINT)).thenReturn(qsConstraints)
|
||||
whenever(motionLayout.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT))
|
||||
.thenReturn(largeScreenConstraints)
|
||||
}
|
||||
|
||||
private fun setUpDefaultInsets() {
|
||||
whenever(combinedShadeHeadersConstraintManager
|
||||
.edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
|
||||
).thenReturn(EMPTY_CHANGES)
|
||||
whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
|
||||
.thenReturn(EMPTY_CHANGES)
|
||||
whenever(combinedShadeHeadersConstraintManager
|
||||
.centerCutoutConstraints(anyBoolean(), anyInt())
|
||||
).thenReturn(EMPTY_CHANGES)
|
||||
whenever(combinedShadeHeadersConstraintManager
|
||||
.privacyChipVisibilityConstraints(anyBoolean())
|
||||
).thenReturn(EMPTY_CHANGES)
|
||||
whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
|
||||
.thenReturn(Pair(0, 0).toAndroidPair())
|
||||
whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(false)
|
||||
}
|
||||
|
||||
private fun<T, U> Pair<T, U>.toAndroidPair(): android.util.Pair<T, U> {
|
||||
return android.util.Pair(first, second)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
package com.android.systemui.statusbar.phone
|
||||
package com.android.systemui.shade
|
||||
|
||||
import android.app.StatusBarManager
|
||||
import android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.util.TypedValue.COMPLEX_UNIT_PX
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.test.filters.SmallTest
|
||||
@@ -19,19 +17,24 @@ import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.qs.HeaderPrivacyIconsController
|
||||
import com.android.systemui.qs.carrier.QSCarrierGroup
|
||||
import com.android.systemui.qs.carrier.QSCarrierGroupController
|
||||
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController
|
||||
import com.android.systemui.statusbar.phone.StatusIconContainer
|
||||
import com.android.systemui.statusbar.policy.FakeConfigurationController
|
||||
import com.android.systemui.statusbar.policy.VariableDateViewController
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.clearInvocations
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import org.mockito.Mockito.verifyZeroInteractions
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@@ -49,10 +52,14 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
|
||||
@Mock private lateinit var batteryMeterView: BatteryMeterView
|
||||
@Mock private lateinit var batteryMeterViewController: BatteryMeterViewController
|
||||
@Mock private lateinit var privacyIconsController: HeaderPrivacyIconsController
|
||||
@Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider
|
||||
@Mock private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
|
||||
@Mock private lateinit var variableDateViewController: VariableDateViewController
|
||||
@Mock private lateinit var dumpManager: DumpManager
|
||||
@Mock private lateinit var combinedShadeHeadersConstraintManager:
|
||||
CombinedShadeHeadersConstraintManager
|
||||
|
||||
@Mock private lateinit var mockedContext: Context
|
||||
@Mock private lateinit var typedArray: TypedArray
|
||||
|
||||
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
|
||||
var viewVisibility = View.GONE
|
||||
@@ -65,7 +72,6 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
|
||||
fun setup() {
|
||||
whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
|
||||
whenever(clock.context).thenReturn(mockedContext)
|
||||
whenever(mockedContext.obtainStyledAttributes(anyInt(), any())).thenReturn(typedArray)
|
||||
whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
|
||||
whenever(date.context).thenReturn(mockedContext)
|
||||
whenever<QSCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
|
||||
@@ -73,6 +79,7 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
|
||||
.thenReturn(batteryMeterView)
|
||||
whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
|
||||
whenever(view.context).thenReturn(context)
|
||||
whenever(view.resources).thenReturn(context.resources)
|
||||
whenever(statusIcons.context).thenReturn(context)
|
||||
whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any()))
|
||||
.thenReturn(qsCarrierGroupControllerBuilder)
|
||||
@@ -82,27 +89,39 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
|
||||
null
|
||||
}
|
||||
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
|
||||
whenever(variableDateViewControllerFactory.create(any()))
|
||||
.thenReturn(variableDateViewController)
|
||||
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false)
|
||||
mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController(
|
||||
view,
|
||||
statusBarIconController,
|
||||
privacyIconsController,
|
||||
insetsProvider,
|
||||
configurationController,
|
||||
qsCarrierGroupControllerBuilder,
|
||||
featureFlags,
|
||||
variableDateViewControllerFactory,
|
||||
batteryMeterViewController,
|
||||
dumpManager
|
||||
dumpManager,
|
||||
featureFlags,
|
||||
qsCarrierGroupControllerBuilder,
|
||||
combinedShadeHeadersConstraintManager
|
||||
)
|
||||
whenever(view.isAttachedToWindow).thenReturn(true)
|
||||
mLargeScreenShadeHeaderController.init()
|
||||
carrierIconSlots = listOf(
|
||||
context.getString(com.android.internal.R.string.status_bar_mobile))
|
||||
}
|
||||
|
||||
@After
|
||||
fun verifyEveryTest() {
|
||||
verifyZeroInteractions(combinedShadeHeadersConstraintManager)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setVisible_onlyWhenActive() {
|
||||
makeShadeVisible()
|
||||
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
|
||||
|
||||
mLargeScreenShadeHeaderController.active = false
|
||||
mLargeScreenShadeHeaderController.largeScreenActive = false
|
||||
assertThat(viewVisibility).isEqualTo(View.GONE)
|
||||
}
|
||||
|
||||
@@ -156,41 +175,16 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
private fun makeShadeVisible() {
|
||||
mLargeScreenShadeHeaderController.active = true
|
||||
mLargeScreenShadeHeaderController.shadeExpanded = true
|
||||
mLargeScreenShadeHeaderController.largeScreenActive = true
|
||||
mLargeScreenShadeHeaderController.qsVisible = true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateConfig_changesFontSize() {
|
||||
val updatedTextPixelSize = 32
|
||||
setReturnTextSize(updatedTextPixelSize)
|
||||
|
||||
fun updateConfig_changesFontStyle() {
|
||||
configurationController.notifyDensityOrFontScaleChanged()
|
||||
|
||||
verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize.toFloat())
|
||||
verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize.toFloat())
|
||||
verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateConfig_changesFontSizeMultipleTimes() {
|
||||
val updatedTextPixelSize1 = 32
|
||||
setReturnTextSize(updatedTextPixelSize1)
|
||||
configurationController.notifyDensityOrFontScaleChanged()
|
||||
verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize1.toFloat())
|
||||
verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize1.toFloat())
|
||||
verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status)
|
||||
clearInvocations(carrierGroup)
|
||||
|
||||
val updatedTextPixelSize2 = 42
|
||||
setReturnTextSize(updatedTextPixelSize2)
|
||||
configurationController.notifyDensityOrFontScaleChanged()
|
||||
verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize2.toFloat())
|
||||
verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize2.toFloat())
|
||||
verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status)
|
||||
}
|
||||
|
||||
private fun setReturnTextSize(resultTextSize: Int) {
|
||||
whenever(typedArray.getDimensionPixelSize(anyInt(), anyInt())).thenReturn(resultTextSize)
|
||||
verify(clock).setTextAppearance(R.style.TextAppearance_QS_Status)
|
||||
verify(date).setTextAppearance(R.style.TextAppearance_QS_Status)
|
||||
verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,6 @@ import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
|
||||
import com.android.systemui.statusbar.phone.KeyguardBypassController;
|
||||
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
|
||||
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
|
||||
import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController;
|
||||
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
|
||||
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
|
||||
import com.android.systemui.statusbar.phone.PanelViewController;
|
||||
@@ -1182,11 +1181,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
|
||||
mStatusBarStateController.setState(SHADE);
|
||||
when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(true);
|
||||
mNotificationPanelViewController.updateResources();
|
||||
verify(mLargeScreenShadeHeaderController).setActive(true);
|
||||
verify(mLargeScreenShadeHeaderController).setLargeScreenActive(true);
|
||||
|
||||
when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(false);
|
||||
mNotificationPanelViewController.updateResources();
|
||||
verify(mLargeScreenShadeHeaderController).setActive(false);
|
||||
verify(mLargeScreenShadeHeaderController).setLargeScreenActive(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user