Merge changes Ia2d6f52d,I578b8257 into tm-qpr-dev
* changes: Remove new headers flag Prepare to remove new headers flag
This commit is contained in:
committed by
Android (Google) Code Review
commit
4f5290d8dc
@@ -1,100 +0,0 @@
|
||||
<?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.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:systemui="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/split_shade_status_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/large_screen_shade_header_height"
|
||||
android:minHeight="@dimen/large_screen_shade_header_min_height"
|
||||
android:clickable="false"
|
||||
android:focusable="true"
|
||||
android:paddingLeft="@dimen/large_screen_shade_header_left_padding"
|
||||
android:paddingRight="@dimen/qs_panel_padding"
|
||||
android:visibility="gone"
|
||||
android:theme="@style/Theme.SystemUI.QuickSettings.Header">
|
||||
|
||||
<com.android.systemui.statusbar.policy.Clock
|
||||
android:id="@+id/clock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:minWidth="48dp"
|
||||
android:minHeight="@dimen/large_screen_shade_header_min_height"
|
||||
android:gravity="start|center_vertical"
|
||||
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
|
||||
android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.QS.Status" />
|
||||
|
||||
<com.android.systemui.statusbar.policy.DateView
|
||||
android:id="@+id/date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.QS.Status"
|
||||
systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/rightLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end|center_vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/carrier_group"
|
||||
layout="@layout/qs_carrier_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
android:focusable="false"
|
||||
android:minHeight="@dimen/large_screen_shade_header_min_height"
|
||||
android:minWidth="48dp" />
|
||||
|
||||
<com.android.systemui.statusbar.phone.StatusIconContainer
|
||||
android:id="@+id/statusIcons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingEnd="@dimen/signal_cluster_battery_padding" />
|
||||
|
||||
<com.android.systemui.battery.BatteryMeterView
|
||||
android:id="@+id/batteryRemainingIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
systemui:textAppearance="@style/TextAppearance.QS.Status" />
|
||||
<FrameLayout
|
||||
android:id="@+id/privacy_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:minHeight="48dp"
|
||||
android:layout_weight="1"
|
||||
android:paddingStart="16dp">
|
||||
|
||||
<include layout="@layout/ongoing_privacy_chip" />
|
||||
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,111 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2017 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.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:systemui="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/quick_qs_status_icons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@*android:dimen/quick_qs_offset_height"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:minHeight="@dimen/qs_header_row_min_height"
|
||||
android:clickable="false"
|
||||
android:focusable="true"
|
||||
android:theme="@style/Theme.SystemUI.QuickSettings.Header">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/clock_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:gravity="center_vertical|start"
|
||||
>
|
||||
|
||||
<com.android.systemui.statusbar.policy.Clock
|
||||
android:id="@+id/clock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:minHeight="@dimen/qs_header_row_min_height"
|
||||
android:gravity="center_vertical|start"
|
||||
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
|
||||
android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.QS.Status" />
|
||||
|
||||
<com.android.systemui.statusbar.policy.VariableDateView
|
||||
android:id="@+id/date_clock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/status_bar_left_clock_end_padding"
|
||||
android:gravity="center_vertical|start"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.QS.Status"
|
||||
systemui:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
|
||||
systemui:shortDatePattern="@string/abbrev_month_day_no_year"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/qs_carrier_group"
|
||||
android:id="@+id/carrier_group"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:minHeight="@dimen/qs_header_row_min_height"
|
||||
android:minWidth="48dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:focusable="false"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/separator"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/rightLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="end"
|
||||
>
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
>
|
||||
<com.android.systemui.statusbar.phone.StatusIconContainer
|
||||
android:id="@+id/statusIcons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingEnd="@dimen/signal_cluster_battery_padding" />
|
||||
|
||||
<com.android.systemui.battery.BatteryMeterView
|
||||
android:id="@+id/batteryRemainingIcon"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
systemui:textAppearance="@style/TextAppearance.QS.Status"
|
||||
android:paddingEnd="2dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -33,32 +33,16 @@
|
||||
android:paddingStart="0dp"
|
||||
android:elevation="4dp" >
|
||||
|
||||
<!-- Date and privacy. Only visible in QS when not in split shade -->
|
||||
<include layout="@layout/quick_status_bar_header_date_privacy"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/qs_container"
|
||||
<com.android.systemui.qs.QuickQSPanel
|
||||
android:id="@+id/quick_qs_panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
android:layout_marginTop="@dimen/qqs_layout_margin_top"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
<!-- Time, icons and Carrier (only in QS when not in split shade) -->
|
||||
<include layout="@layout/quick_qs_status_icons"/>
|
||||
|
||||
<com.android.systemui.qs.QuickQSPanel
|
||||
android:id="@+id/quick_qs_panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/quick_qs_status_icons"
|
||||
android:layout_marginTop="@dimen/qqs_layout_margin_top"
|
||||
android:accessibilityTraversalAfter="@id/quick_qs_status_icons"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:focusable="true"
|
||||
android:paddingBottom="@dimen/qqs_layout_padding_bottom"
|
||||
android:importantForAccessibility="no">
|
||||
</com.android.systemui.qs.QuickQSPanel>
|
||||
</RelativeLayout>
|
||||
android:clipToPadding="false"
|
||||
android:focusable="true"
|
||||
android:paddingBottom="@dimen/qqs_layout_padding_bottom"
|
||||
android:importantForAccessibility="no">
|
||||
</com.android.systemui.qs.QuickQSPanel>
|
||||
|
||||
</com.android.systemui.qs.QuickStatusBarHeader>
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
** Copyright 2017, 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.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:systemui="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/quick_status_bar_date_privacy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@*android:dimen/quick_qs_offset_height"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="top"
|
||||
android:orientation="horizontal"
|
||||
android:importantForAccessibility="no"
|
||||
android:clickable="true"
|
||||
android:minHeight="48dp">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/date_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:minHeight="48dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical|start" >
|
||||
|
||||
<com.android.systemui.statusbar.policy.VariableDateView
|
||||
android:id="@+id/date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.QS.Status"
|
||||
systemui:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
|
||||
systemui:shortDatePattern="@string/abbrev_month_day_no_year"
|
||||
/>
|
||||
</FrameLayout>
|
||||
|
||||
<!-- We want this to be centered (to align with notches). In order to do that, the following
|
||||
has to hold (in portrait):
|
||||
* date_container and privacy_container must have the same width and weight
|
||||
-->
|
||||
<android.widget.Space
|
||||
android:id="@+id/space"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical|center_horizontal"
|
||||
android:visibility="gone" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/privacy_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:minHeight="48dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical|end" >
|
||||
|
||||
<include layout="@layout/ongoing_privacy_chip" />
|
||||
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
@@ -63,7 +63,6 @@
|
||||
<dimen name="large_dialog_width">348dp</dimen>
|
||||
|
||||
<dimen name="qs_panel_padding_top">@dimen/qqs_layout_margin_top</dimen>
|
||||
<dimen name="qs_panel_padding_top_combined_headers">@dimen/qs_panel_padding_top</dimen>
|
||||
|
||||
<dimen name="controls_padding_horizontal">16dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -62,7 +62,6 @@
|
||||
<dimen name="qs_security_footer_background_inset">0dp</dimen>
|
||||
|
||||
<dimen name="qs_panel_padding_top">8dp</dimen>
|
||||
<dimen name="qs_panel_padding_top_combined_headers">@dimen/qs_panel_padding_top</dimen>
|
||||
|
||||
<!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
|
||||
<dimen name="large_dialog_width">472dp</dimen>
|
||||
|
||||
@@ -616,8 +616,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_combined_headers">80dp</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>
|
||||
|
||||
@@ -237,8 +237,6 @@ object Flags {
|
||||
|
||||
// 500 - quick settings
|
||||
|
||||
// TODO(b/254512321): Tracking Bug
|
||||
@JvmField val COMBINED_QS_HEADERS = releasedFlag(501, "combined_qs_headers")
|
||||
val PEOPLE_TILE = resourceBooleanFlag(502, R.bool.flag_conversations, "people_tile")
|
||||
|
||||
@JvmField
|
||||
|
||||
@@ -330,12 +330,8 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener
|
||||
|
||||
// Offset the translation animation on the views
|
||||
// (that goes from 0 to getOffsetTranslation)
|
||||
int offsetWithQSBHTranslation =
|
||||
yOffset - mQuickStatusBarHeader.getOffsetTranslation();
|
||||
qqsTranslationYBuilder.addFloat(quickTileView, "translationY", 0,
|
||||
offsetWithQSBHTranslation);
|
||||
translationYBuilder.addFloat(tileView, "translationY",
|
||||
-offsetWithQSBHTranslation, 0);
|
||||
qqsTranslationYBuilder.addFloat(quickTileView, "translationY", 0, yOffset);
|
||||
translationYBuilder.addFloat(tileView, "translationY", -yOffset, 0);
|
||||
|
||||
translationXBuilder.addFloat(quickTileView, "translationX", 0, xOffset);
|
||||
translationXBuilder.addFloat(tileView, "translationX", -xOffset, 0);
|
||||
@@ -614,7 +610,7 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener
|
||||
View commonView = mQs.getView();
|
||||
getRelativePositionInt(qsPosition, view1, commonView);
|
||||
getRelativePositionInt(qqsPosition, view2, commonView);
|
||||
return (qsPosition[1] - qqsPosition[1]) - mQuickStatusBarHeader.getOffsetTranslation();
|
||||
return qsPosition[1] - qqsPosition[1];
|
||||
}
|
||||
|
||||
private boolean isIconInAnimatedRow(int count) {
|
||||
|
||||
@@ -53,7 +53,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
|
||||
private boolean mQsDisabled;
|
||||
private int mContentHorizontalPadding = -1;
|
||||
private boolean mClippingEnabled;
|
||||
private boolean mUseCombinedHeaders;
|
||||
|
||||
public QSContainerImpl(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
@@ -68,10 +67,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
|
||||
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||
}
|
||||
|
||||
void setUseCombinedHeaders(boolean useCombinedHeaders) {
|
||||
mUseCombinedHeaders = useCombinedHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasOverlappingRendering() {
|
||||
return false;
|
||||
@@ -150,8 +145,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
|
||||
void updateResources(QSPanelController qsPanelController,
|
||||
QuickStatusBarHeaderController quickStatusBarHeaderController) {
|
||||
int topPadding = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
|
||||
if (mUseCombinedHeaders
|
||||
&& !LargeScreenUtils.shouldUseLargeScreenShadeHeader(mContext.getResources())) {
|
||||
if (!LargeScreenUtils.shouldUseLargeScreenShadeHeader(mContext.getResources())) {
|
||||
topPadding = mContext.getResources()
|
||||
.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
|
||||
}
|
||||
|
||||
@@ -22,8 +22,6 @@ import android.content.res.Configuration;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.flags.Flags;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.qs.dagger.QSScope;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
@@ -66,15 +64,13 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> {
|
||||
QSPanelController qsPanelController,
|
||||
QuickStatusBarHeaderController quickStatusBarHeaderController,
|
||||
ConfigurationController configurationController,
|
||||
FalsingManager falsingManager,
|
||||
FeatureFlags featureFlags) {
|
||||
FalsingManager falsingManager) {
|
||||
super(view);
|
||||
mQsPanelController = qsPanelController;
|
||||
mQuickStatusBarHeaderController = quickStatusBarHeaderController;
|
||||
mConfigurationController = configurationController;
|
||||
mFalsingManager = falsingManager;
|
||||
mQSPanelContainer = mView.getQSPanelContainer();
|
||||
view.setUseCombinedHeaders(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -228,7 +228,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
(v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
|
||||
// Lazily update animators whenever the scrolling changes
|
||||
mQSAnimator.requestAnimatorUpdate();
|
||||
mHeader.setExpandedScrollAmount(scrollY);
|
||||
if (mScrollListener != null) {
|
||||
mScrollListener.onQsPanelScrollChanged(scrollY);
|
||||
}
|
||||
@@ -648,8 +647,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
int heightDiff = getHeightDiff();
|
||||
float panelTranslationY = translationScaleY * heightDiff;
|
||||
|
||||
// Let the views animate their contents correctly by giving them the necessary context.
|
||||
mHeader.setExpansion(onKeyguardAndExpanded, expansion, panelTranslationY);
|
||||
if (expansion < 1 && expansion > 0.99) {
|
||||
if (mQuickQSPanelController.switchTileLayout(false)) {
|
||||
mHeader.updateResources();
|
||||
|
||||
@@ -104,7 +104,6 @@ public class QSPanel extends LinearLayout implements Tunable {
|
||||
private final Rect mClippingRect = new Rect();
|
||||
private ViewGroup mMediaHostView;
|
||||
private boolean mShouldMoveMediaOnExpansion = true;
|
||||
private boolean mUsingCombinedHeaders = false;
|
||||
private QSLogger mQsLogger;
|
||||
/**
|
||||
* Specifies if we can collapse to QQS in current state. In split shade that should be always
|
||||
@@ -155,10 +154,6 @@ public class QSPanel extends LinearLayout implements Tunable {
|
||||
}
|
||||
}
|
||||
|
||||
void setUsingCombinedHeaders(boolean usingCombinedHeaders) {
|
||||
mUsingCombinedHeaders = usingCombinedHeaders;
|
||||
}
|
||||
|
||||
protected void setHorizontalContentContainerClipping() {
|
||||
mHorizontalContentContainer.setClipChildren(true);
|
||||
mHorizontalContentContainer.setClipToPadding(false);
|
||||
@@ -383,9 +378,7 @@ public class QSPanel extends LinearLayout implements Tunable {
|
||||
|
||||
protected void updatePadding() {
|
||||
final Resources res = mContext.getResources();
|
||||
int paddingTop = res.getDimensionPixelSize(
|
||||
mUsingCombinedHeaders ? R.dimen.qs_panel_padding_top_combined_headers
|
||||
: R.dimen.qs_panel_padding_top);
|
||||
int paddingTop = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top);
|
||||
int paddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
|
||||
setPaddingRelative(getPaddingStart(),
|
||||
paddingTop,
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.systemui.qs;
|
||||
|
||||
import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
|
||||
import static com.android.systemui.flags.Flags.COMBINED_QS_HEADERS;
|
||||
import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
|
||||
import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
|
||||
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
|
||||
@@ -28,7 +27,6 @@ import android.view.View;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.UiEventLogger;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
|
||||
import com.android.systemui.media.controls.ui.MediaHost;
|
||||
import com.android.systemui.media.controls.ui.MediaHostState;
|
||||
@@ -81,8 +79,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
|
||||
QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
|
||||
BrightnessSliderController.Factory brightnessSliderFactory,
|
||||
FalsingManager falsingManager,
|
||||
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
|
||||
FeatureFlags featureFlags) {
|
||||
StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
|
||||
super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
|
||||
metricsLogger, uiEventLogger, qsLogger, dumpManager);
|
||||
mTunerService = tunerService;
|
||||
@@ -96,7 +93,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
|
||||
mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
|
||||
mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
|
||||
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
|
||||
mView.setUsingCombinedHeaders(featureFlags.isEnabled(COMBINED_QS_HEADERS));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,35 +20,15 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Pair;
|
||||
import android.view.DisplayCutout;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Space;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.policy.SystemBarUtils;
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.battery.BatteryMeterView;
|
||||
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
|
||||
import com.android.systemui.statusbar.phone.StatusIconContainer;
|
||||
import com.android.systemui.statusbar.policy.Clock;
|
||||
import com.android.systemui.statusbar.policy.VariableDateView;
|
||||
import com.android.systemui.util.LargeScreenUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* View that contains the top-most bits of the QS panel (primarily the status bar with date, time,
|
||||
* battery, carrier info and privacy icons) and also contains the {@link QuickQSPanel}.
|
||||
@@ -58,182 +38,30 @@ public class QuickStatusBarHeader extends FrameLayout {
|
||||
private boolean mExpanded;
|
||||
private boolean mQsDisabled;
|
||||
|
||||
@Nullable
|
||||
private TouchAnimator mAlphaAnimator;
|
||||
@Nullable
|
||||
private TouchAnimator mTranslationAnimator;
|
||||
@Nullable
|
||||
private TouchAnimator mIconsAlphaAnimator;
|
||||
private TouchAnimator mIconsAlphaAnimatorFixed;
|
||||
|
||||
protected QuickQSPanel mHeaderQsPanel;
|
||||
private View mDatePrivacyView;
|
||||
private View mDateView;
|
||||
// DateView next to clock. Visible on QQS
|
||||
private VariableDateView mClockDateView;
|
||||
private View mStatusIconsView;
|
||||
private View mContainer;
|
||||
|
||||
private View mQSCarriers;
|
||||
private ViewGroup mClockContainer;
|
||||
private Clock mClockView;
|
||||
private Space mDatePrivacySeparator;
|
||||
private View mClockIconsSeparator;
|
||||
private boolean mShowClockIconsSeparator;
|
||||
private View mRightLayout;
|
||||
private View mDateContainer;
|
||||
private View mPrivacyContainer;
|
||||
|
||||
private BatteryMeterView mBatteryRemainingIcon;
|
||||
private StatusIconContainer mIconContainer;
|
||||
private View mPrivacyChip;
|
||||
|
||||
@Nullable
|
||||
private TintedIconManager mTintedIconManager;
|
||||
@Nullable
|
||||
private QSExpansionPathInterpolator mQSExpansionPathInterpolator;
|
||||
private StatusBarContentInsetsProvider mInsetsProvider;
|
||||
|
||||
private int mRoundedCornerPadding = 0;
|
||||
private int mWaterfallTopInset;
|
||||
private int mCutOutPaddingLeft;
|
||||
private int mCutOutPaddingRight;
|
||||
private float mKeyguardExpansionFraction;
|
||||
private int mTextColorPrimary = Color.TRANSPARENT;
|
||||
private int mTopViewMeasureHeight;
|
||||
|
||||
@NonNull
|
||||
private List<String> mRssiIgnoredSlots = List.of();
|
||||
private boolean mIsSingleCarrier;
|
||||
|
||||
private boolean mHasCenterCutout;
|
||||
private boolean mConfigShowBatteryEstimate;
|
||||
|
||||
private boolean mUseCombinedQSHeader;
|
||||
|
||||
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* How much the view containing the clock and QQS will translate down when QS is fully expanded.
|
||||
*
|
||||
* This matches the measured height of the view containing the date and privacy icons.
|
||||
*/
|
||||
public int getOffsetTranslation() {
|
||||
return mTopViewMeasureHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
mHeaderQsPanel = findViewById(R.id.quick_qs_panel);
|
||||
mDatePrivacyView = findViewById(R.id.quick_status_bar_date_privacy);
|
||||
mStatusIconsView = findViewById(R.id.quick_qs_status_icons);
|
||||
mQSCarriers = findViewById(R.id.carrier_group);
|
||||
mContainer = findViewById(R.id.qs_container);
|
||||
mIconContainer = findViewById(R.id.statusIcons);
|
||||
mPrivacyChip = findViewById(R.id.privacy_chip);
|
||||
mDateView = findViewById(R.id.date);
|
||||
mClockDateView = findViewById(R.id.date_clock);
|
||||
mClockIconsSeparator = findViewById(R.id.separator);
|
||||
mRightLayout = findViewById(R.id.rightLayout);
|
||||
mDateContainer = findViewById(R.id.date_container);
|
||||
mPrivacyContainer = findViewById(R.id.privacy_container);
|
||||
|
||||
mClockContainer = findViewById(R.id.clock_container);
|
||||
mClockView = findViewById(R.id.clock);
|
||||
mDatePrivacySeparator = findViewById(R.id.space);
|
||||
// Tint for the battery icons are handled in setupHost()
|
||||
mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon);
|
||||
|
||||
updateResources();
|
||||
Configuration config = mContext.getResources().getConfiguration();
|
||||
setDatePrivacyContainersWidth(config.orientation == Configuration.ORIENTATION_LANDSCAPE);
|
||||
|
||||
updateBatteryMode();
|
||||
|
||||
mIconsAlphaAnimatorFixed = new TouchAnimator.Builder()
|
||||
.addFloat(mIconContainer, "alpha", 0, 1)
|
||||
.addFloat(mBatteryRemainingIcon, "alpha", 0, 1)
|
||||
.build();
|
||||
}
|
||||
|
||||
void onAttach(TintedIconManager iconManager,
|
||||
QSExpansionPathInterpolator qsExpansionPathInterpolator,
|
||||
List<String> rssiIgnoredSlots,
|
||||
StatusBarContentInsetsProvider insetsProvider,
|
||||
boolean useCombinedQSHeader) {
|
||||
mUseCombinedQSHeader = useCombinedQSHeader;
|
||||
mTintedIconManager = iconManager;
|
||||
mRssiIgnoredSlots = rssiIgnoredSlots;
|
||||
mInsetsProvider = insetsProvider;
|
||||
int fillColor = Utils.getColorAttrDefaultColor(getContext(),
|
||||
android.R.attr.textColorPrimary);
|
||||
|
||||
// Set the correct tint for the status icons so they contrast
|
||||
iconManager.setTint(fillColor);
|
||||
|
||||
mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
|
||||
updateAnimators();
|
||||
}
|
||||
|
||||
void setIsSingleCarrier(boolean isSingleCarrier) {
|
||||
mIsSingleCarrier = isSingleCarrier;
|
||||
updateAlphaAnimator();
|
||||
}
|
||||
|
||||
public QuickQSPanel getHeaderQsPanel() {
|
||||
return mHeaderQsPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
if (mDatePrivacyView.getMeasuredHeight() != mTopViewMeasureHeight) {
|
||||
mTopViewMeasureHeight = mDatePrivacyView.getMeasuredHeight();
|
||||
post(this::updateAnimators);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
updateResources();
|
||||
setDatePrivacyContainersWidth(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRtlPropertiesChanged(int layoutDirection) {
|
||||
super.onRtlPropertiesChanged(layoutDirection);
|
||||
updateResources();
|
||||
}
|
||||
|
||||
private void setDatePrivacyContainersWidth(boolean landscape) {
|
||||
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mDateContainer.getLayoutParams();
|
||||
lp.width = landscape ? WRAP_CONTENT : 0;
|
||||
lp.weight = landscape ? 0f : 1f;
|
||||
mDateContainer.setLayoutParams(lp);
|
||||
|
||||
lp = (LinearLayout.LayoutParams) mPrivacyContainer.getLayoutParams();
|
||||
lp.width = landscape ? WRAP_CONTENT : 0;
|
||||
lp.weight = landscape ? 0f : 1f;
|
||||
mPrivacyContainer.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
private void updateBatteryMode() {
|
||||
if (mConfigShowBatteryEstimate && !mHasCenterCutout) {
|
||||
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
|
||||
} else {
|
||||
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ON);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// If using combined headers, only react to touches inside QuickQSPanel
|
||||
if (!mUseCombinedQSHeader || event.getY() > mHeaderQsPanel.getTop()) {
|
||||
// Only react to touches inside QuickQSPanel
|
||||
if (event.getY() > mHeaderQsPanel.getTop()) {
|
||||
return super.onTouchEvent(event);
|
||||
} else {
|
||||
return false;
|
||||
@@ -245,193 +73,29 @@ public class QuickStatusBarHeader extends FrameLayout {
|
||||
boolean largeScreenHeaderActive =
|
||||
LargeScreenUtils.shouldUseLargeScreenShadeHeader(resources);
|
||||
|
||||
boolean gone = largeScreenHeaderActive || mUseCombinedQSHeader || mQsDisabled;
|
||||
mStatusIconsView.setVisibility(gone ? View.GONE : View.VISIBLE);
|
||||
mDatePrivacyView.setVisibility(gone ? View.GONE : View.VISIBLE);
|
||||
|
||||
mConfigShowBatteryEstimate = resources.getBoolean(R.bool.config_showBatteryEstimateQSBH);
|
||||
|
||||
mRoundedCornerPadding = resources.getDimensionPixelSize(
|
||||
R.dimen.rounded_corner_content_padding);
|
||||
|
||||
int qsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(mContext);
|
||||
|
||||
mDatePrivacyView.getLayoutParams().height =
|
||||
Math.max(qsOffsetHeight, mDatePrivacyView.getMinimumHeight());
|
||||
mDatePrivacyView.setLayoutParams(mDatePrivacyView.getLayoutParams());
|
||||
|
||||
mStatusIconsView.getLayoutParams().height =
|
||||
Math.max(qsOffsetHeight, mStatusIconsView.getMinimumHeight());
|
||||
mStatusIconsView.setLayoutParams(mStatusIconsView.getLayoutParams());
|
||||
|
||||
ViewGroup.LayoutParams lp = getLayoutParams();
|
||||
if (mQsDisabled) {
|
||||
lp.height = mStatusIconsView.getLayoutParams().height;
|
||||
lp.height = 0;
|
||||
} else {
|
||||
lp.height = WRAP_CONTENT;
|
||||
}
|
||||
setLayoutParams(lp);
|
||||
|
||||
int textColor = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
|
||||
if (textColor != mTextColorPrimary) {
|
||||
int textColorSecondary = Utils.getColorAttrDefaultColor(mContext,
|
||||
android.R.attr.textColorSecondary);
|
||||
mTextColorPrimary = textColor;
|
||||
mClockView.setTextColor(textColor);
|
||||
if (mTintedIconManager != null) {
|
||||
mTintedIconManager.setTint(textColor);
|
||||
}
|
||||
mBatteryRemainingIcon.updateColors(mTextColorPrimary, textColorSecondary,
|
||||
mTextColorPrimary);
|
||||
}
|
||||
|
||||
MarginLayoutParams qqsLP = (MarginLayoutParams) mHeaderQsPanel.getLayoutParams();
|
||||
if (largeScreenHeaderActive) {
|
||||
qqsLP.topMargin = mContext.getResources()
|
||||
.getDimensionPixelSize(R.dimen.qqs_layout_margin_top);
|
||||
} else if (!mUseCombinedQSHeader) {
|
||||
qqsLP.topMargin = qsOffsetHeight;
|
||||
} else {
|
||||
qqsLP.topMargin = mContext.getResources()
|
||||
.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height);
|
||||
}
|
||||
mHeaderQsPanel.setLayoutParams(qqsLP);
|
||||
|
||||
updateBatteryMode();
|
||||
updateHeadersPadding();
|
||||
updateAnimators();
|
||||
|
||||
updateClockDatePadding();
|
||||
}
|
||||
|
||||
private void updateClockDatePadding() {
|
||||
int startPadding = mContext.getResources()
|
||||
.getDimensionPixelSize(R.dimen.status_bar_left_clock_starting_padding);
|
||||
int endPadding = mContext.getResources()
|
||||
.getDimensionPixelSize(R.dimen.status_bar_left_clock_end_padding);
|
||||
mClockView.setPaddingRelative(
|
||||
startPadding,
|
||||
mClockView.getPaddingTop(),
|
||||
endPadding,
|
||||
mClockView.getPaddingBottom()
|
||||
);
|
||||
|
||||
MarginLayoutParams lp = (MarginLayoutParams) mClockDateView.getLayoutParams();
|
||||
lp.setMarginStart(endPadding);
|
||||
mClockDateView.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
private void updateAnimators() {
|
||||
if (mUseCombinedQSHeader) {
|
||||
mTranslationAnimator = null;
|
||||
return;
|
||||
}
|
||||
updateAlphaAnimator();
|
||||
int offset = mTopViewMeasureHeight;
|
||||
|
||||
mTranslationAnimator = new TouchAnimator.Builder()
|
||||
.addFloat(mContainer, "translationY", 0, offset)
|
||||
.setInterpolator(mQSExpansionPathInterpolator != null
|
||||
? mQSExpansionPathInterpolator.getYInterpolator()
|
||||
: null)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void updateAlphaAnimator() {
|
||||
if (mUseCombinedQSHeader) {
|
||||
mAlphaAnimator = null;
|
||||
return;
|
||||
}
|
||||
TouchAnimator.Builder builder = new TouchAnimator.Builder()
|
||||
// These views appear on expanding down
|
||||
.addFloat(mDateView, "alpha", 0, 0, 1)
|
||||
.addFloat(mClockDateView, "alpha", 1, 0, 0)
|
||||
.addFloat(mQSCarriers, "alpha", 0, 1)
|
||||
.setListener(new TouchAnimator.ListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationAtEnd() {
|
||||
super.onAnimationAtEnd();
|
||||
if (!mIsSingleCarrier) {
|
||||
mIconContainer.addIgnoredSlots(mRssiIgnoredSlots);
|
||||
}
|
||||
// Make it gone so there's enough room for carrier names
|
||||
mClockDateView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStarted() {
|
||||
mClockDateView.setVisibility(View.VISIBLE);
|
||||
mClockDateView.setFreezeSwitching(true);
|
||||
setSeparatorVisibility(false);
|
||||
if (!mIsSingleCarrier) {
|
||||
mIconContainer.addIgnoredSlots(mRssiIgnoredSlots);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationAtStart() {
|
||||
super.onAnimationAtStart();
|
||||
mClockDateView.setFreezeSwitching(false);
|
||||
mClockDateView.setVisibility(View.VISIBLE);
|
||||
setSeparatorVisibility(mShowClockIconsSeparator);
|
||||
// In QQS we never ignore RSSI.
|
||||
mIconContainer.removeIgnoredSlots(mRssiIgnoredSlots);
|
||||
}
|
||||
});
|
||||
mAlphaAnimator = builder.build();
|
||||
}
|
||||
|
||||
void setChipVisibility(boolean visibility) {
|
||||
if (visibility) {
|
||||
// Animates the icons and battery indicator from alpha 0 to 1, when the chip is visible
|
||||
mIconsAlphaAnimator = mIconsAlphaAnimatorFixed;
|
||||
mIconsAlphaAnimator.setPosition(mKeyguardExpansionFraction);
|
||||
} else {
|
||||
mIconsAlphaAnimator = null;
|
||||
mIconContainer.setAlpha(1);
|
||||
mBatteryRemainingIcon.setAlpha(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** */
|
||||
public void setExpanded(boolean expanded, QuickQSPanelController quickQSPanelController) {
|
||||
if (mExpanded == expanded) return;
|
||||
mExpanded = expanded;
|
||||
quickQSPanelController.setExpanded(expanded);
|
||||
updateEverything();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the inner contents based on the given expansion details.
|
||||
*
|
||||
* @param forceExpanded whether we should show the state expanded forcibly
|
||||
* @param expansionFraction how much the QS panel is expanded/pulled out (up to 1f)
|
||||
* @param panelTranslationY how much the panel has physically moved down vertically (required
|
||||
* for keyguard animations only)
|
||||
*/
|
||||
public void setExpansion(boolean forceExpanded, float expansionFraction,
|
||||
float panelTranslationY) {
|
||||
final float keyguardExpansionFraction = forceExpanded ? 1f : expansionFraction;
|
||||
|
||||
if (mAlphaAnimator != null) {
|
||||
mAlphaAnimator.setPosition(keyguardExpansionFraction);
|
||||
}
|
||||
if (mTranslationAnimator != null) {
|
||||
mTranslationAnimator.setPosition(keyguardExpansionFraction);
|
||||
}
|
||||
if (mIconsAlphaAnimator != null) {
|
||||
mIconsAlphaAnimator.setPosition(keyguardExpansionFraction);
|
||||
}
|
||||
// If forceExpanded (we are opening QS from lockscreen), the animators have been set to
|
||||
// position = 1f.
|
||||
if (forceExpanded) {
|
||||
setAlpha(expansionFraction);
|
||||
} else {
|
||||
setAlpha(1);
|
||||
}
|
||||
|
||||
mKeyguardExpansionFraction = keyguardExpansionFraction;
|
||||
}
|
||||
|
||||
public void disable(int state1, int state2, boolean animate) {
|
||||
@@ -439,133 +103,13 @@ public class QuickStatusBarHeader extends FrameLayout {
|
||||
if (disabled == mQsDisabled) return;
|
||||
mQsDisabled = disabled;
|
||||
mHeaderQsPanel.setDisabledByPolicy(disabled);
|
||||
mStatusIconsView.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
|
||||
updateResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
|
||||
// Handle padding of the views
|
||||
DisplayCutout cutout = insets.getDisplayCutout();
|
||||
|
||||
Pair<Integer, Integer> sbInsets = mInsetsProvider
|
||||
.getStatusBarContentInsetsForCurrentRotation();
|
||||
boolean hasCornerCutout = mInsetsProvider.currentRotationHasCornerCutout();
|
||||
|
||||
mDatePrivacyView.setPadding(sbInsets.first, 0, sbInsets.second, 0);
|
||||
mStatusIconsView.setPadding(sbInsets.first, 0, sbInsets.second, 0);
|
||||
LinearLayout.LayoutParams datePrivacySeparatorLayoutParams =
|
||||
(LinearLayout.LayoutParams) mDatePrivacySeparator.getLayoutParams();
|
||||
LinearLayout.LayoutParams mClockIconsSeparatorLayoutParams =
|
||||
(LinearLayout.LayoutParams) mClockIconsSeparator.getLayoutParams();
|
||||
|
||||
Rect topCutout = cutout == null ? null : cutout.getBoundingRectTop();
|
||||
if (topCutout == null || topCutout.isEmpty() || hasCornerCutout) {
|
||||
datePrivacySeparatorLayoutParams.width = 0;
|
||||
mDatePrivacySeparator.setVisibility(View.GONE);
|
||||
mClockIconsSeparatorLayoutParams.width = 0;
|
||||
setSeparatorVisibility(false);
|
||||
mShowClockIconsSeparator = false;
|
||||
mHasCenterCutout = false;
|
||||
} else {
|
||||
datePrivacySeparatorLayoutParams.width = topCutout.width();
|
||||
mDatePrivacySeparator.setVisibility(View.VISIBLE);
|
||||
mClockIconsSeparatorLayoutParams.width = topCutout.width();
|
||||
mShowClockIconsSeparator = true;
|
||||
setSeparatorVisibility(mKeyguardExpansionFraction == 0f);
|
||||
mHasCenterCutout = true;
|
||||
}
|
||||
|
||||
mDatePrivacySeparator.setLayoutParams(datePrivacySeparatorLayoutParams);
|
||||
mClockIconsSeparator.setLayoutParams(mClockIconsSeparatorLayoutParams);
|
||||
mCutOutPaddingLeft = sbInsets.first;
|
||||
mCutOutPaddingRight = sbInsets.second;
|
||||
mWaterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top;
|
||||
|
||||
updateBatteryMode();
|
||||
updateHeadersPadding();
|
||||
return super.onApplyWindowInsets(insets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the visibility of the separator between clock and icons.
|
||||
*
|
||||
* This separator is "visible" when there is a center cutout, to block that space. In that
|
||||
* case, the clock and the layout on the right (containing the icons and the battery meter) are
|
||||
* set to weight 1 to take the available space.
|
||||
* @param visible whether the separator between clock and icons should be visible.
|
||||
*/
|
||||
private void setSeparatorVisibility(boolean visible) {
|
||||
int newVisibility = visible ? View.VISIBLE : View.GONE;
|
||||
if (mClockIconsSeparator.getVisibility() == newVisibility) return;
|
||||
|
||||
mClockIconsSeparator.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
mQSCarriers.setVisibility(visible ? View.GONE : View.VISIBLE);
|
||||
|
||||
LinearLayout.LayoutParams lp =
|
||||
(LinearLayout.LayoutParams) mClockContainer.getLayoutParams();
|
||||
lp.width = visible ? 0 : WRAP_CONTENT;
|
||||
lp.weight = visible ? 1f : 0f;
|
||||
mClockContainer.setLayoutParams(lp);
|
||||
|
||||
lp = (LinearLayout.LayoutParams) mRightLayout.getLayoutParams();
|
||||
lp.width = visible ? 0 : WRAP_CONTENT;
|
||||
lp.weight = visible ? 1f : 0f;
|
||||
mRightLayout.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
private void updateHeadersPadding() {
|
||||
setContentMargins(mDatePrivacyView, 0, 0);
|
||||
setContentMargins(mStatusIconsView, 0, 0);
|
||||
int paddingLeft = 0;
|
||||
int paddingRight = 0;
|
||||
|
||||
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
|
||||
int leftMargin = lp.leftMargin;
|
||||
int rightMargin = lp.rightMargin;
|
||||
|
||||
// The clock might collide with cutouts, let's shift it out of the way.
|
||||
// We only do that if the inset is bigger than our own padding, since it's nicer to
|
||||
// align with
|
||||
if (mCutOutPaddingLeft > 0) {
|
||||
// if there's a cutout, let's use at least the rounded corner inset
|
||||
int cutoutPadding = Math.max(mCutOutPaddingLeft, mRoundedCornerPadding);
|
||||
paddingLeft = Math.max(cutoutPadding - leftMargin, 0);
|
||||
}
|
||||
if (mCutOutPaddingRight > 0) {
|
||||
// if there's a cutout, let's use at least the rounded corner inset
|
||||
int cutoutPadding = Math.max(mCutOutPaddingRight, mRoundedCornerPadding);
|
||||
paddingRight = Math.max(cutoutPadding - rightMargin, 0);
|
||||
}
|
||||
|
||||
mDatePrivacyView.setPadding(paddingLeft,
|
||||
mWaterfallTopInset,
|
||||
paddingRight,
|
||||
0);
|
||||
mStatusIconsView.setPadding(paddingLeft,
|
||||
mWaterfallTopInset,
|
||||
paddingRight,
|
||||
0);
|
||||
}
|
||||
|
||||
public void updateEverything() {
|
||||
post(() -> setClickable(!mExpanded));
|
||||
}
|
||||
|
||||
private void setContentMargins(View view, int marginStart, int marginEnd) {
|
||||
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
|
||||
lp.setMarginStart(marginStart);
|
||||
lp.setMarginEnd(marginEnd);
|
||||
view.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the headers away.
|
||||
*
|
||||
* @param scrollY the scroll of the QSPanel container
|
||||
*/
|
||||
public void setExpandedScrollAmount(int scrollY) {
|
||||
mStatusIconsView.setScrollY(scrollY);
|
||||
mDatePrivacyView.setScrollY(scrollY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,154 +16,38 @@
|
||||
|
||||
package com.android.systemui.qs;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.internal.colorextraction.ColorExtractor;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.battery.BatteryMeterViewController;
|
||||
import com.android.systemui.colorextraction.SysuiColorExtractor;
|
||||
import com.android.systemui.demomode.DemoMode;
|
||||
import com.android.systemui.demomode.DemoModeController;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.flags.Flags;
|
||||
import com.android.systemui.qs.carrier.QSCarrierGroupController;
|
||||
import com.android.systemui.qs.dagger.QSScope;
|
||||
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController;
|
||||
import com.android.systemui.statusbar.phone.StatusBarLocation;
|
||||
import com.android.systemui.statusbar.phone.StatusIconContainer;
|
||||
import com.android.systemui.statusbar.policy.Clock;
|
||||
import com.android.systemui.statusbar.policy.VariableDateViewController;
|
||||
import com.android.systemui.util.ViewController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Controller for {@link QuickStatusBarHeader}.
|
||||
*/
|
||||
@QSScope
|
||||
class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader> implements
|
||||
ChipVisibilityListener {
|
||||
class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader> {
|
||||
|
||||
private final QSCarrierGroupController mQSCarrierGroupController;
|
||||
private final QuickQSPanelController mQuickQSPanelController;
|
||||
private final Clock mClockView;
|
||||
private final StatusBarIconController mStatusBarIconController;
|
||||
private final DemoModeController mDemoModeController;
|
||||
private final StatusIconContainer mIconContainer;
|
||||
private final StatusBarIconController.TintedIconManager mIconManager;
|
||||
private final DemoMode mDemoModeReceiver;
|
||||
private final QSExpansionPathInterpolator mQSExpansionPathInterpolator;
|
||||
private final FeatureFlags mFeatureFlags;
|
||||
private final BatteryMeterViewController mBatteryMeterViewController;
|
||||
private final StatusBarContentInsetsProvider mInsetsProvider;
|
||||
|
||||
private final VariableDateViewController mVariableDateViewControllerDateView;
|
||||
private final VariableDateViewController mVariableDateViewControllerClockDateView;
|
||||
private final HeaderPrivacyIconsController mPrivacyIconsController;
|
||||
|
||||
private boolean mListening;
|
||||
|
||||
private SysuiColorExtractor mColorExtractor;
|
||||
private ColorExtractor.OnColorsChangedListener mOnColorsChangedListener;
|
||||
|
||||
@Inject
|
||||
QuickStatusBarHeaderController(QuickStatusBarHeader view,
|
||||
HeaderPrivacyIconsController headerPrivacyIconsController,
|
||||
StatusBarIconController statusBarIconController,
|
||||
DemoModeController demoModeController,
|
||||
QuickQSPanelController quickQSPanelController,
|
||||
QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder,
|
||||
SysuiColorExtractor colorExtractor,
|
||||
QSExpansionPathInterpolator qsExpansionPathInterpolator,
|
||||
FeatureFlags featureFlags,
|
||||
VariableDateViewController.Factory variableDateViewControllerFactory,
|
||||
BatteryMeterViewController batteryMeterViewController,
|
||||
StatusBarContentInsetsProvider statusBarContentInsetsProvider,
|
||||
StatusBarIconController.TintedIconManager.Factory tintedIconManagerFactory) {
|
||||
QuickQSPanelController quickQSPanelController
|
||||
) {
|
||||
super(view);
|
||||
mPrivacyIconsController = headerPrivacyIconsController;
|
||||
mStatusBarIconController = statusBarIconController;
|
||||
mDemoModeController = demoModeController;
|
||||
mQuickQSPanelController = quickQSPanelController;
|
||||
mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
|
||||
mFeatureFlags = featureFlags;
|
||||
mBatteryMeterViewController = batteryMeterViewController;
|
||||
mInsetsProvider = statusBarContentInsetsProvider;
|
||||
|
||||
mQSCarrierGroupController = qsCarrierGroupControllerBuilder
|
||||
.setQSCarrierGroup(mView.findViewById(R.id.carrier_group))
|
||||
.build();
|
||||
mClockView = mView.findViewById(R.id.clock);
|
||||
mIconContainer = mView.findViewById(R.id.statusIcons);
|
||||
mVariableDateViewControllerDateView = variableDateViewControllerFactory.create(
|
||||
mView.requireViewById(R.id.date)
|
||||
);
|
||||
mVariableDateViewControllerClockDateView = variableDateViewControllerFactory.create(
|
||||
mView.requireViewById(R.id.date_clock)
|
||||
);
|
||||
|
||||
mIconManager = tintedIconManagerFactory.create(mIconContainer, StatusBarLocation.QS);
|
||||
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
|
||||
mColorExtractor = colorExtractor;
|
||||
mOnColorsChangedListener = (extractor, which) -> {
|
||||
final boolean lightTheme = mColorExtractor.getNeutralColors().supportsDarkText();
|
||||
mClockView.onColorsChanged(lightTheme);
|
||||
};
|
||||
mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener);
|
||||
|
||||
// Don't need to worry about tuner settings for this icon
|
||||
mBatteryMeterViewController.ignoreTunerUpdates();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
mBatteryMeterViewController.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewAttached() {
|
||||
mPrivacyIconsController.onParentVisible();
|
||||
mPrivacyIconsController.setChipVisibilityListener(this);
|
||||
mIconContainer.addIgnoredSlot(
|
||||
getResources().getString(com.android.internal.R.string.status_bar_managed_profile));
|
||||
mIconContainer.addIgnoredSlot(
|
||||
getResources().getString(com.android.internal.R.string.status_bar_alarm_clock));
|
||||
mIconContainer.setShouldRestrictIcons(false);
|
||||
mStatusBarIconController.addIconGroup(mIconManager);
|
||||
|
||||
mView.setIsSingleCarrier(mQSCarrierGroupController.isSingleCarrier());
|
||||
mQSCarrierGroupController
|
||||
.setOnSingleCarrierChangedListener(mView::setIsSingleCarrier);
|
||||
|
||||
List<String> rssiIgnoredSlots = List.of(
|
||||
getResources().getString(com.android.internal.R.string.status_bar_mobile)
|
||||
);
|
||||
|
||||
mView.onAttach(mIconManager, mQSExpansionPathInterpolator, rssiIgnoredSlots,
|
||||
mInsetsProvider, mFeatureFlags.isEnabled(Flags.COMBINED_QS_HEADERS));
|
||||
|
||||
mDemoModeController.addCallback(mDemoModeReceiver);
|
||||
|
||||
mVariableDateViewControllerDateView.init();
|
||||
mVariableDateViewControllerClockDateView.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewDetached() {
|
||||
mColorExtractor.removeOnColorsChangedListener(mOnColorsChangedListener);
|
||||
mPrivacyIconsController.onParentInvisible();
|
||||
mStatusBarIconController.removeIconGroup(mIconManager);
|
||||
mQSCarrierGroupController.setOnSingleCarrierChangedListener(null);
|
||||
mDemoModeController.removeCallback(mDemoModeReceiver);
|
||||
setListening(false);
|
||||
}
|
||||
|
||||
public void setListening(boolean listening) {
|
||||
mQSCarrierGroupController.setListening(listening);
|
||||
|
||||
if (listening == mListening) {
|
||||
return;
|
||||
}
|
||||
@@ -174,48 +58,9 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
|
||||
if (mQuickQSPanelController.switchTileLayout(false)) {
|
||||
mView.updateResources();
|
||||
}
|
||||
|
||||
if (listening) {
|
||||
mPrivacyIconsController.startListening();
|
||||
} else {
|
||||
mPrivacyIconsController.stopListening();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChipVisibilityRefreshed(boolean visible) {
|
||||
mView.setChipVisibility(visible);
|
||||
}
|
||||
|
||||
public void setContentMargins(int marginStart, int marginEnd) {
|
||||
mQuickQSPanelController.setContentMargins(marginStart, marginEnd);
|
||||
}
|
||||
|
||||
private static class ClockDemoModeReceiver implements DemoMode {
|
||||
private Clock mClockView;
|
||||
|
||||
@Override
|
||||
public List<String> demoCommands() {
|
||||
return List.of(COMMAND_CLOCK);
|
||||
}
|
||||
|
||||
ClockDemoModeReceiver(Clock clockView) {
|
||||
mClockView = clockView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchDemoCommand(String command, Bundle args) {
|
||||
mClockView.dispatchDemoCommand(command, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDemoModeStarted() {
|
||||
mClockView.onDemoModeStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDemoModeFinished() {
|
||||
mClockView.onDemoModeFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,8 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.battery.BatteryMeterView;
|
||||
import com.android.systemui.dagger.qualifiers.RootView;
|
||||
import com.android.systemui.plugins.qs.QS;
|
||||
import com.android.systemui.privacy.OngoingPrivacyChip;
|
||||
import com.android.systemui.qs.QSContainerImpl;
|
||||
import com.android.systemui.qs.QSFooter;
|
||||
import com.android.systemui.qs.QSFooterView;
|
||||
@@ -37,7 +35,6 @@ import com.android.systemui.qs.QSPanel;
|
||||
import com.android.systemui.qs.QuickQSPanel;
|
||||
import com.android.systemui.qs.QuickStatusBarHeader;
|
||||
import com.android.systemui.qs.customize.QSCustomizer;
|
||||
import com.android.systemui.statusbar.phone.StatusIconContainer;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
@@ -104,12 +101,6 @@ public interface QSFragmentModule {
|
||||
return quickStatusBarHeader.findViewById(R.id.quick_qs_panel);
|
||||
}
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
static BatteryMeterView providesBatteryMeterView(QuickStatusBarHeader quickStatusBarHeader) {
|
||||
return quickStatusBarHeader.findViewById(R.id.batteryRemainingIcon);
|
||||
}
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
static QSFooterView providesQSFooterView(@RootView View view) {
|
||||
@@ -144,18 +135,4 @@ public interface QSFragmentModule {
|
||||
static boolean providesQSUsingCollapsedLandscapeMedia(Context context) {
|
||||
return useCollapsedMediaInLandscape(context.getResources());
|
||||
}
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
@QSScope
|
||||
static OngoingPrivacyChip providesPrivacyChip(QuickStatusBarHeader qsHeader) {
|
||||
return qsHeader.findViewById(R.id.privacy_chip);
|
||||
}
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
@QSScope
|
||||
static StatusIconContainer providesStatusIconContainer(QuickStatusBarHeader qsHeader) {
|
||||
return qsHeader.findViewById(R.id.statusIcons);
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ operator fun ConstraintChange?.plus(other: ConstraintChange?): ConstraintChange?
|
||||
|
||||
/**
|
||||
* Contains all changes that need to be performed to the different [ConstraintSet] in
|
||||
* [LargeScreenShadeHeaderController].
|
||||
* [ShadeHeaderController].
|
||||
*/
|
||||
data class ConstraintsChanges(
|
||||
val qqsConstraintsChanges: ConstraintChange? = null,
|
||||
@@ -46,7 +46,7 @@ data class ConstraintsChanges(
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines [ConstraintChanges] for [LargeScreenShadeHeaderController] based on configurations.
|
||||
* Determines [ConstraintChanges] for [ShadeHeaderController] 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.
|
||||
|
||||
@@ -48,7 +48,10 @@ constructor(
|
||||
setOf(
|
||||
ViewIdToTranslate(R.id.quick_settings_panel, START, filterShade),
|
||||
ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade),
|
||||
ViewIdToTranslate(R.id.rightLayout, END, filterShade),
|
||||
ViewIdToTranslate(R.id.statusIcons, END, filterShade),
|
||||
ViewIdToTranslate(R.id.privacy_container, END, filterShade),
|
||||
ViewIdToTranslate(R.id.batteryRemainingIcon, END, filterShade),
|
||||
ViewIdToTranslate(R.id.carrier_group, END, filterShade),
|
||||
ViewIdToTranslate(R.id.clock, START, filterShade),
|
||||
ViewIdToTranslate(R.id.date, START, filterShade)),
|
||||
progressProvider = progressProvider)
|
||||
|
||||
@@ -336,7 +336,7 @@ public final class NotificationPanelViewController implements Dumpable {
|
||||
private final ScrimController mScrimController;
|
||||
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
|
||||
private final TapAgainViewController mTapAgainViewController;
|
||||
private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
|
||||
private final ShadeHeaderController mShadeHeaderController;
|
||||
private final boolean mVibrateOnOpening;
|
||||
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
|
||||
private final FlingAnimationUtils mFlingAnimationUtilsClosing;
|
||||
@@ -693,7 +693,7 @@ public final class NotificationPanelViewController implements Dumpable {
|
||||
FragmentService fragmentService,
|
||||
ContentResolver contentResolver,
|
||||
RecordingController recordingController,
|
||||
LargeScreenShadeHeaderController largeScreenShadeHeaderController,
|
||||
ShadeHeaderController shadeHeaderController,
|
||||
ScreenOffAnimationController screenOffAnimationController,
|
||||
LockscreenGestureLogger lockscreenGestureLogger,
|
||||
ShadeExpansionStateManager shadeExpansionStateManager,
|
||||
@@ -813,7 +813,7 @@ public final class NotificationPanelViewController implements Dumpable {
|
||||
mSplitShadeEnabled =
|
||||
LargeScreenUtils.shouldUseSplitNotificationShade(mResources);
|
||||
mView.setWillNotDraw(!DEBUG_DRAWABLE);
|
||||
mLargeScreenShadeHeaderController = largeScreenShadeHeaderController;
|
||||
mShadeHeaderController = shadeHeaderController;
|
||||
mLayoutInflater = layoutInflater;
|
||||
mFeatureFlags = featureFlags;
|
||||
mFalsingCollector = falsingCollector;
|
||||
@@ -1040,7 +1040,7 @@ public final class NotificationPanelViewController implements Dumpable {
|
||||
}
|
||||
|
||||
mTapAgainViewController.init();
|
||||
mLargeScreenShadeHeaderController.init();
|
||||
mShadeHeaderController.init();
|
||||
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
|
||||
mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
|
||||
controller.setup(mNotificationContainerParent));
|
||||
@@ -3337,7 +3337,7 @@ public final class NotificationPanelViewController implements Dumpable {
|
||||
}
|
||||
|
||||
public void disable(int state1, int state2, boolean animated) {
|
||||
mLargeScreenShadeHeaderController.disable(state1, state2, animated);
|
||||
mShadeHeaderController.disable(state1, state2, animated);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,8 +29,6 @@ import androidx.constraintlayout.widget.ConstraintSet.START
|
||||
import androidx.constraintlayout.widget.ConstraintSet.TOP
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.fragments.FragmentService
|
||||
import com.android.systemui.navigationbar.NavigationModeController
|
||||
import com.android.systemui.plugins.qs.QS
|
||||
@@ -49,14 +47,13 @@ import kotlin.reflect.KMutableProperty0
|
||||
internal const val INSET_DEBOUNCE_MILLIS = 500L
|
||||
|
||||
class NotificationsQSContainerController @Inject constructor(
|
||||
view: NotificationsQuickSettingsContainer,
|
||||
private val navigationModeController: NavigationModeController,
|
||||
private val overviewProxyService: OverviewProxyService,
|
||||
private val largeScreenShadeHeaderController: LargeScreenShadeHeaderController,
|
||||
private val shadeExpansionStateManager: ShadeExpansionStateManager,
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val fragmentService: FragmentService,
|
||||
@Main private val delayableExecutor: DelayableExecutor
|
||||
view: NotificationsQuickSettingsContainer,
|
||||
private val navigationModeController: NavigationModeController,
|
||||
private val overviewProxyService: OverviewProxyService,
|
||||
private val shadeHeaderController: ShadeHeaderController,
|
||||
private val shadeExpansionStateManager: ShadeExpansionStateManager,
|
||||
private val fragmentService: FragmentService,
|
||||
@Main private val delayableExecutor: DelayableExecutor
|
||||
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
|
||||
|
||||
private var qsExpanded = false
|
||||
@@ -75,8 +72,6 @@ class NotificationsQSContainerController @Inject constructor(
|
||||
private var panelMarginHorizontal = 0
|
||||
private var topMargin = 0
|
||||
|
||||
private val useCombinedQSHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
|
||||
|
||||
private var isGestureNavigation = true
|
||||
private var taskbarVisible = false
|
||||
private val taskbarVisibilityListener: OverviewProxyListener = object : OverviewProxyListener {
|
||||
@@ -184,7 +179,7 @@ class NotificationsQSContainerController @Inject constructor(
|
||||
override fun setCustomizerShowing(showing: Boolean, animationDuration: Long) {
|
||||
if (showing != isQSCustomizing) {
|
||||
isQSCustomizing = showing
|
||||
largeScreenShadeHeaderController.startCustomizingAnimation(showing, animationDuration)
|
||||
shadeHeaderController.startCustomizingAnimation(showing, animationDuration)
|
||||
updateBottomSpacing()
|
||||
}
|
||||
}
|
||||
@@ -250,9 +245,7 @@ class NotificationsQSContainerController @Inject constructor(
|
||||
if (largeScreenShadeHeaderActive) {
|
||||
constraintSet.constrainHeight(R.id.split_shade_status_bar, largeScreenShadeHeaderHeight)
|
||||
} else {
|
||||
if (useCombinedQSHeaders) {
|
||||
constraintSet.constrainHeight(R.id.split_shade_status_bar, WRAP_CONTENT)
|
||||
}
|
||||
constraintSet.constrainHeight(R.id.split_shade_status_bar, WRAP_CONTENT)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ import com.android.systemui.animation.Interpolators;
|
||||
import com.android.systemui.classifier.Classifier;
|
||||
import com.android.systemui.classifier.FalsingCollector;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.flags.Flags;
|
||||
import com.android.systemui.fragments.FragmentHostManager;
|
||||
import com.android.systemui.media.controls.pipeline.MediaDataManager;
|
||||
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
|
||||
@@ -110,7 +109,7 @@ public class QuickSettingsController {
|
||||
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
|
||||
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
|
||||
private final NotificationShadeDepthController mDepthController;
|
||||
private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
|
||||
private final ShadeHeaderController mShadeHeaderController;
|
||||
private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
|
||||
private final KeyguardStateController mKeyguardStateController;
|
||||
private final KeyguardBypassController mKeyguardBypassController;
|
||||
@@ -276,7 +275,7 @@ public class QuickSettingsController {
|
||||
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
|
||||
LockscreenShadeTransitionController lockscreenShadeTransitionController,
|
||||
NotificationShadeDepthController notificationShadeDepthController,
|
||||
LargeScreenShadeHeaderController largeScreenShadeHeaderController,
|
||||
ShadeHeaderController shadeHeaderController,
|
||||
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
|
||||
KeyguardStateController keyguardStateController,
|
||||
KeyguardBypassController keyguardBypassController,
|
||||
@@ -315,7 +314,7 @@ public class QuickSettingsController {
|
||||
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
|
||||
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
|
||||
mDepthController = notificationShadeDepthController;
|
||||
mLargeScreenShadeHeaderController = largeScreenShadeHeaderController;
|
||||
mShadeHeaderController = shadeHeaderController;
|
||||
mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
|
||||
mKeyguardStateController = keyguardStateController;
|
||||
mKeyguardBypassController = keyguardBypassController;
|
||||
@@ -396,18 +395,10 @@ public class QuickSettingsController {
|
||||
mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
|
||||
int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
|
||||
mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
|
||||
mLargeScreenShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
|
||||
mShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
|
||||
mAmbientState.setStackTopMargin(topMargin);
|
||||
|
||||
// TODO: When the flag is eventually removed, it means that we have a single view that is
|
||||
// the same height in QQS and in Large Screen (large_screen_shade_header_height). Eventually
|
||||
// the concept of largeScreenHeader or quickQsHeader will disappear outside of the class
|
||||
// that controls the view as the offset needs to be the same regardless.
|
||||
if (mUseLargeScreenShadeHeader || mFeatureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)) {
|
||||
mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight;
|
||||
} else {
|
||||
mQuickQsHeaderHeight = SystemBarUtils.getQuickQsOffsetHeight(mPanelView.getContext());
|
||||
}
|
||||
mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight;
|
||||
|
||||
mEnableClipping = mResources.getBoolean(R.bool.qs_enable_clipping);
|
||||
}
|
||||
@@ -931,9 +922,9 @@ public class QuickSettingsController {
|
||||
float shadeExpandedFraction = mBarState == KEYGUARD
|
||||
? mPanelViewControllerLazy.get().getLockscreenShadeDragProgress()
|
||||
: mShadeExpandedFraction;
|
||||
mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
|
||||
mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
|
||||
mLargeScreenShadeHeaderController.setQsVisible(mVisible);
|
||||
mShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
|
||||
mShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
|
||||
mShadeHeaderController.setQsVisible(mVisible);
|
||||
}
|
||||
|
||||
/** */
|
||||
@@ -1402,7 +1393,7 @@ public class QuickSettingsController {
|
||||
}
|
||||
|
||||
private void onScroll(int scrollY) {
|
||||
mLargeScreenShadeHeaderController.setQsScrollY(scrollY);
|
||||
mShadeHeaderController.setQsScrollY(scrollY);
|
||||
if (scrollY > 0 && !mFullyExpanded) {
|
||||
// TODO (b/265193930): remove dependency on NPVC
|
||||
// If we are scrolling QS, we should be fully expanded.
|
||||
|
||||
@@ -41,22 +41,21 @@ import com.android.systemui.battery.BatteryMeterViewController
|
||||
import com.android.systemui.demomode.DemoMode
|
||||
import com.android.systemui.demomode.DemoModeController
|
||||
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.QQS_HEADER_CONSTRAINT
|
||||
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
|
||||
import com.android.systemui.shade.ShadeHeaderController.Companion.HEADER_TRANSITION_ID
|
||||
import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
|
||||
import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
|
||||
import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
|
||||
import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
|
||||
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController
|
||||
import com.android.systemui.statusbar.phone.StatusBarLocation
|
||||
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.phone.dagger.StatusBarViewModule.SHADE_HEADER
|
||||
import com.android.systemui.statusbar.policy.Clock
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.statusbar.policy.VariableDateView
|
||||
@@ -67,30 +66,27 @@ import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
/**
|
||||
* Controller for QS header on Large Screen width (large screen + landscape).
|
||||
* Controller for QS header.
|
||||
*
|
||||
* 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 one transitions:
|
||||
* [header] is a [MotionLayout] that has two transitions:
|
||||
* * [HEADER_TRANSITION_ID]: [QQS_HEADER_CONSTRAINT] <-> [QS_HEADER_CONSTRAINT] for portrait
|
||||
* handheld device configuration.
|
||||
* handheld device configuration.
|
||||
* * [LARGE_SCREEN_HEADER_TRANSITION_ID]: [LARGE_SCREEN_HEADER_CONSTRAINT] for all other
|
||||
* configurations
|
||||
*/
|
||||
@CentralSurfacesScope
|
||||
class LargeScreenShadeHeaderController @Inject constructor(
|
||||
@Named(LARGE_SCREEN_SHADE_HEADER) private val header: View,
|
||||
class ShadeHeaderController
|
||||
@Inject
|
||||
constructor(
|
||||
@Named(SHADE_HEADER) private val header: MotionLayout,
|
||||
private val statusBarIconController: StatusBarIconController,
|
||||
private val tintedIconManagerFactory: StatusBarIconController.TintedIconManager.Factory,
|
||||
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,
|
||||
@Named(SHADE_HEADER) private val batteryMeterViewController: BatteryMeterViewController,
|
||||
private val dumpManager: DumpManager,
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
|
||||
private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager,
|
||||
private val demoModeController: DemoModeController,
|
||||
@@ -98,30 +94,24 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
) : 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
|
||||
/** IDs for transitions and constraints for the [MotionLayout]. */
|
||||
@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 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 $this"
|
||||
}
|
||||
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 $this"
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -180,9 +170,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
onHeaderStateChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Expansion fraction of the QQS/QS shade. This is not the expansion between QQS <-> QS.
|
||||
*/
|
||||
/** Expansion fraction of the QQS/QS shade. This is not the expansion between QQS <-> QS. */
|
||||
var shadeExpandedFraction = -1f
|
||||
set(value) {
|
||||
if (qsVisible && field != value) {
|
||||
@@ -191,9 +179,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expansion fraction of the QQS <-> QS animation.
|
||||
*/
|
||||
/** Expansion fraction of the QQS <-> QS animation. */
|
||||
var qsExpandedFraction = -1f
|
||||
set(value) {
|
||||
if (visible && field != value) {
|
||||
@@ -202,9 +188,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current scroll of QS.
|
||||
*/
|
||||
/** Current scroll of QS. */
|
||||
var qsScrollY = 0
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
@@ -213,40 +197,41 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private val insetListener = View.OnApplyWindowInsetsListener { view, insets ->
|
||||
updateConstraintsForInsets(view as MotionLayout, insets)
|
||||
lastInsets = WindowInsets(insets)
|
||||
private val insetListener =
|
||||
View.OnApplyWindowInsetsListener { view, insets ->
|
||||
updateConstraintsForInsets(view as MotionLayout, insets)
|
||||
lastInsets = WindowInsets(insets)
|
||||
|
||||
view.onApplyWindowInsets(insets)
|
||||
}
|
||||
view.onApplyWindowInsets(insets)
|
||||
}
|
||||
|
||||
private val demoModeReceiver = object : DemoMode {
|
||||
override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
|
||||
override fun dispatchDemoCommand(command: String, args: Bundle) =
|
||||
clock.dispatchDemoCommand(command, args)
|
||||
override fun onDemoModeStarted() = clock.onDemoModeStarted()
|
||||
override fun onDemoModeFinished() = clock.onDemoModeFinished()
|
||||
}
|
||||
private val demoModeReceiver =
|
||||
object : DemoMode {
|
||||
override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
|
||||
override fun dispatchDemoCommand(command: String, args: Bundle) =
|
||||
clock.dispatchDemoCommand(command, args)
|
||||
override fun onDemoModeStarted() = clock.onDemoModeStarted()
|
||||
override fun onDemoModeFinished() = clock.onDemoModeFinished()
|
||||
}
|
||||
|
||||
private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
|
||||
override fun onChipVisibilityRefreshed(visible: Boolean) {
|
||||
if (header is MotionLayout) {
|
||||
private val chipVisibilityListener: ChipVisibilityListener =
|
||||
object : ChipVisibilityListener {
|
||||
override fun onChipVisibilityRefreshed(visible: Boolean) {
|
||||
// If the privacy chip is visible, we hide the status icons and battery remaining
|
||||
// icon, only in QQS.
|
||||
val update = combinedShadeHeadersConstraintManager
|
||||
.privacyChipVisibilityConstraints(visible)
|
||||
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
|
||||
)
|
||||
override fun onConfigChanged(newConfig: Configuration?) {
|
||||
val left =
|
||||
header.resources.getDimensionPixelSize(
|
||||
R.dimen.large_screen_shade_header_left_padding
|
||||
)
|
||||
header.setPadding(
|
||||
left,
|
||||
header.paddingTop,
|
||||
@@ -254,26 +239,21 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
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) {
|
||||
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)
|
||||
loadConstraints()
|
||||
header.minHeight = resources
|
||||
.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height)
|
||||
header.minHeight =
|
||||
resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height)
|
||||
lastInsets?.let { updateConstraintsForInsets(header, it) }
|
||||
updateResources()
|
||||
}
|
||||
updateResources()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInit() {
|
||||
if (header is MotionLayout) {
|
||||
variableDateViewControllerFactory.create(date as VariableDateView).init()
|
||||
}
|
||||
variableDateViewControllerFactory.create(date as VariableDateView).init()
|
||||
batteryMeterViewController.init()
|
||||
|
||||
// battery settings same as in QS icons
|
||||
@@ -286,20 +266,10 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
|
||||
carrierIconSlots =
|
||||
listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
|
||||
qsCarrierGroupController = qsCarrierGroupControllerBuilder
|
||||
.setQSCarrierGroup(qsCarrierGroup)
|
||||
.build()
|
||||
qsCarrierGroupController =
|
||||
qsCarrierGroupControllerBuilder.setQSCarrierGroup(qsCarrierGroup).build()
|
||||
|
||||
if (!combinedHeaders) {
|
||||
// In the new header, we display alarm icon but we ignore it when not using the new
|
||||
// headers.
|
||||
iconContainer.addIgnoredSlot(
|
||||
context.getString(com.android.internal.R.string.status_bar_alarm_clock)
|
||||
)
|
||||
}
|
||||
if (combinedHeaders) {
|
||||
privacyIconsController.onParentVisible()
|
||||
}
|
||||
privacyIconsController.onParentVisible()
|
||||
}
|
||||
|
||||
override fun onViewAttached() {
|
||||
@@ -307,16 +277,14 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
updateVisibility()
|
||||
updateTransition()
|
||||
|
||||
if (header is MotionLayout) {
|
||||
header.setOnApplyWindowInsetsListener(insetListener)
|
||||
header.setOnApplyWindowInsetsListener(insetListener)
|
||||
|
||||
clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
|
||||
val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
|
||||
v.pivotX = newPivot
|
||||
v.pivotY = v.height.toFloat() / 2
|
||||
clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
|
||||
val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
|
||||
v.pivotX = newPivot
|
||||
v.pivotY = v.height.toFloat() / 2
|
||||
|
||||
qsCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0)
|
||||
}
|
||||
qsCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0)
|
||||
}
|
||||
|
||||
dumpManager.registerDumpable(this)
|
||||
@@ -341,30 +309,30 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
}
|
||||
|
||||
fun startCustomizingAnimation(show: Boolean, duration: Long) {
|
||||
header.animate()
|
||||
.setDuration(duration)
|
||||
.alpha(if (show) 0f else 1f)
|
||||
.setInterpolator(if (show) Interpolators.ALPHA_OUT else Interpolators.ALPHA_IN)
|
||||
.setListener(CustomizerAnimationListener(show))
|
||||
.start()
|
||||
header
|
||||
.animate()
|
||||
.setDuration(duration)
|
||||
.alpha(if (show) 0f else 1f)
|
||||
.setInterpolator(if (show) Interpolators.ALPHA_OUT else Interpolators.ALPHA_IN)
|
||||
.setListener(CustomizerAnimationListener(show))
|
||||
.start()
|
||||
}
|
||||
|
||||
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))
|
||||
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))
|
||||
}
|
||||
// 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))
|
||||
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))
|
||||
}
|
||||
|
||||
private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) {
|
||||
val cutout = insets.displayCutout.also {
|
||||
this.cutout = it
|
||||
}
|
||||
val cutout = insets.displayCutout.also { this.cutout = it }
|
||||
|
||||
val sbInsets: Pair<Int, Int> = insetsProvider.getStatusBarContentInsetsForCurrentRotation()
|
||||
val cutoutLeft = sbInsets.first
|
||||
@@ -373,8 +341,8 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
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(
|
||||
var changes =
|
||||
combinedShadeHeadersConstraintManager.edgesGuidelinesConstraints(
|
||||
if (view.isLayoutRtl) cutoutRight else cutoutLeft,
|
||||
header.paddingStart,
|
||||
if (view.isLayoutRtl) cutoutLeft else cutoutRight,
|
||||
@@ -386,13 +354,14 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
if (topCutout.isEmpty || hasCornerCutout) {
|
||||
changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
|
||||
} else {
|
||||
changes += combinedShadeHeadersConstraintManager.centerCutoutConstraints(
|
||||
view.isLayoutRtl,
|
||||
(view.width - view.paddingLeft - view.paddingRight - topCutout.width()) / 2
|
||||
)
|
||||
changes +=
|
||||
combinedShadeHeadersConstraintManager.centerCutoutConstraints(
|
||||
view.isLayoutRtl,
|
||||
(view.width - view.paddingLeft - view.paddingRight - topCutout.width()) / 2
|
||||
)
|
||||
}
|
||||
} else {
|
||||
changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
|
||||
changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
|
||||
}
|
||||
|
||||
view.updateAllConstraints(changes)
|
||||
@@ -406,7 +375,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
}
|
||||
|
||||
private fun updateScrollY() {
|
||||
if (!largeScreenActive && combinedHeaders) {
|
||||
if (!largeScreenActive) {
|
||||
header.scrollY = qsScrollY
|
||||
}
|
||||
}
|
||||
@@ -422,12 +391,6 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
}
|
||||
|
||||
private fun onHeaderStateChanged() {
|
||||
if (largeScreenActive || combinedHeaders) {
|
||||
privacyIconsController.onParentVisible()
|
||||
} else {
|
||||
privacyIconsController.onParentInvisible()
|
||||
}
|
||||
updateVisibility()
|
||||
updateTransition()
|
||||
}
|
||||
|
||||
@@ -436,13 +399,14 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
* be visible any time the QQS/QS shade is open.
|
||||
*/
|
||||
private fun updateVisibility() {
|
||||
val visibility = if (!largeScreenActive && !combinedHeaders || qsDisabled) {
|
||||
View.GONE
|
||||
} else if (qsVisible && !customizing) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.INVISIBLE
|
||||
}
|
||||
val visibility =
|
||||
if (qsDisabled) {
|
||||
View.GONE
|
||||
} else if (qsVisible && !customizing) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.INVISIBLE
|
||||
}
|
||||
if (header.visibility != visibility) {
|
||||
header.visibility = visibility
|
||||
visible = visibility == View.VISIBLE
|
||||
@@ -450,10 +414,6 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
}
|
||||
|
||||
private fun updateTransition() {
|
||||
if (!combinedHeaders) {
|
||||
return
|
||||
}
|
||||
header as MotionLayout
|
||||
if (largeScreenActive) {
|
||||
logInstantEvent("Large screen constraints set")
|
||||
header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
|
||||
@@ -467,7 +427,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
}
|
||||
|
||||
private fun updatePosition() {
|
||||
if (header is MotionLayout && !largeScreenActive && visible) {
|
||||
if (!largeScreenActive && visible) {
|
||||
logInstantEvent("updatePosition: $qsExpandedFraction")
|
||||
header.progress = qsExpandedFraction
|
||||
updateBatteryMode()
|
||||
@@ -475,11 +435,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
}
|
||||
|
||||
private fun logInstantEvent(message: String) {
|
||||
Trace.instantForTrack(
|
||||
TRACE_TAG_APP,
|
||||
"LargeScreenHeaderController",
|
||||
message
|
||||
)
|
||||
Trace.instantForTrack(TRACE_TAG_APP, "LargeScreenHeaderController", message)
|
||||
}
|
||||
|
||||
private fun updateListeners() {
|
||||
@@ -509,18 +465,16 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
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>) {
|
||||
@@ -530,10 +484,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
pw.println("active: $largeScreenActive")
|
||||
pw.println("qsExpandedFraction: $qsExpandedFraction")
|
||||
pw.println("qsScrollY: $qsScrollY")
|
||||
if (combinedHeaders) {
|
||||
header as MotionLayout
|
||||
pw.println("currentState: ${header.currentState.stateToString()}")
|
||||
}
|
||||
pw.println("currentState: ${header.currentState.stateToString()}")
|
||||
}
|
||||
|
||||
private fun MotionLayout.updateConstraints(@IdRes state: Int, update: ConstraintChange) {
|
||||
@@ -559,11 +510,10 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun simulateViewDetached() = this.onViewDetached()
|
||||
@VisibleForTesting internal fun simulateViewDetached() = this.onViewDetached()
|
||||
|
||||
inner class CustomizerAnimationListener(
|
||||
private val enteringCustomizing: Boolean,
|
||||
private val enteringCustomizing: Boolean,
|
||||
) : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
super.onAnimationEnd(animation)
|
||||
@@ -22,11 +22,11 @@ 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;
|
||||
import com.android.systemui.shade.QuickSettingsController;
|
||||
import com.android.systemui.shade.ShadeHeaderController;
|
||||
import com.android.systemui.statusbar.NotificationPresenter;
|
||||
import com.android.systemui.statusbar.NotificationShelfController;
|
||||
import com.android.systemui.statusbar.core.StatusBarInitializer;
|
||||
@@ -138,9 +138,9 @@ public interface CentralSurfacesComponent {
|
||||
CentralSurfacesCommandQueueCallbacks getCentralSurfacesCommandQueueCallbacks();
|
||||
|
||||
/**
|
||||
* Creates a {@link LargeScreenShadeHeaderController}.
|
||||
* Creates a {@link ShadeHeaderController}.
|
||||
*/
|
||||
LargeScreenShadeHeaderController getLargeScreenShadeHeaderController();
|
||||
ShadeHeaderController getLargeScreenShadeHeaderController();
|
||||
|
||||
/**
|
||||
* Creates a new {@link CollapsedStatusBarFragment} each time it's called. See
|
||||
|
||||
@@ -20,9 +20,10 @@ import android.annotation.Nullable;
|
||||
import android.content.ContentResolver;
|
||||
import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewStub;
|
||||
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout;
|
||||
|
||||
import com.android.keyguard.KeyguardUpdateMonitor;
|
||||
import com.android.keyguard.LockIconView;
|
||||
import com.android.systemui.R;
|
||||
@@ -32,7 +33,6 @@ import com.android.systemui.biometrics.AuthRippleView;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
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.settings.UserTracker;
|
||||
@@ -85,9 +85,7 @@ import dagger.multibindings.IntoSet;
|
||||
@Module(subcomponents = StatusBarFragmentComponent.class)
|
||||
public abstract class StatusBarViewModule {
|
||||
|
||||
public static final String LARGE_SCREEN_SHADE_HEADER = "large_screen_shade_header";
|
||||
private static final String SPLIT_SHADE_BATTERY_VIEW = "split_shade_battery_view";
|
||||
public static final String LARGE_SCREEN_BATTERY_CONTROLLER = "split_shade_battery_controller";
|
||||
public static final String SHADE_HEADER = "large_screen_shade_header";
|
||||
public static final String STATUS_BAR_FRAGMENT = "status_bar_fragment";
|
||||
|
||||
/** */
|
||||
@@ -171,17 +169,15 @@ public abstract class StatusBarViewModule {
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
@Named(LARGE_SCREEN_SHADE_HEADER)
|
||||
@Named(SHADE_HEADER)
|
||||
@CentralSurfacesComponent.CentralSurfacesScope
|
||||
public static View getLargeScreenShadeHeaderBarView(
|
||||
public static MotionLayout getLargeScreenShadeHeaderBarView(
|
||||
NotificationShadeWindowView notificationShadeWindowView,
|
||||
FeatureFlags featureFlags) {
|
||||
ViewStub stub = notificationShadeWindowView.findViewById(R.id.qs_header_stub);
|
||||
int layoutId = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
|
||||
? R.layout.combined_qs_header
|
||||
: R.layout.large_screen_shade_header;
|
||||
int layoutId = R.layout.combined_qs_header;
|
||||
stub.setLayoutResource(layoutId);
|
||||
View v = stub.inflate();
|
||||
MotionLayout v = (MotionLayout) stub.inflate();
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -197,7 +193,7 @@ public abstract class StatusBarViewModule {
|
||||
@Provides
|
||||
@CentralSurfacesComponent.CentralSurfacesScope
|
||||
public static OngoingPrivacyChip getSplitShadeOngoingPrivacyChip(
|
||||
@Named(LARGE_SCREEN_SHADE_HEADER) View header) {
|
||||
@Named(SHADE_HEADER) MotionLayout header) {
|
||||
return header.findViewById(R.id.privacy_chip);
|
||||
}
|
||||
|
||||
@@ -205,23 +201,23 @@ public abstract class StatusBarViewModule {
|
||||
@Provides
|
||||
@CentralSurfacesComponent.CentralSurfacesScope
|
||||
static StatusIconContainer providesStatusIconContainer(
|
||||
@Named(LARGE_SCREEN_SHADE_HEADER) View header) {
|
||||
@Named(SHADE_HEADER) MotionLayout header) {
|
||||
return header.findViewById(R.id.statusIcons);
|
||||
}
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
@CentralSurfacesComponent.CentralSurfacesScope
|
||||
@Named(SPLIT_SHADE_BATTERY_VIEW)
|
||||
static BatteryMeterView getBatteryMeterView(@Named(LARGE_SCREEN_SHADE_HEADER) View view) {
|
||||
@Named(SHADE_HEADER)
|
||||
static BatteryMeterView getBatteryMeterView(@Named(SHADE_HEADER) MotionLayout view) {
|
||||
return view.findViewById(R.id.batteryRemainingIcon);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@CentralSurfacesComponent.CentralSurfacesScope
|
||||
@Named(LARGE_SCREEN_BATTERY_CONTROLLER)
|
||||
@Named(SHADE_HEADER)
|
||||
static BatteryMeterViewController getBatteryMeterViewController(
|
||||
@Named(SPLIT_SHADE_BATTERY_VIEW) BatteryMeterView batteryMeterView,
|
||||
@Named(SHADE_HEADER) BatteryMeterView batteryMeterView,
|
||||
UserTracker userTracker,
|
||||
ConfigurationController configurationController,
|
||||
TunerService tunerService,
|
||||
|
||||
@@ -9,7 +9,6 @@ import com.android.internal.logging.UiEventLogger
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.media.controls.ui.MediaHost
|
||||
import com.android.systemui.media.controls.ui.MediaHostState
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
@@ -57,7 +56,6 @@ class QSPanelControllerTest : SysuiTestCase() {
|
||||
@Mock private lateinit var tile: QSTile
|
||||
@Mock private lateinit var otherTile: QSTile
|
||||
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
|
||||
@Mock private lateinit var featureFlags: FeatureFlags
|
||||
@Mock private lateinit var configuration: Configuration
|
||||
@Mock private lateinit var pagedTileLayout: PagedTileLayout
|
||||
|
||||
@@ -93,8 +91,7 @@ class QSPanelControllerTest : SysuiTestCase() {
|
||||
brightnessControllerFactory,
|
||||
brightnessSliderFactory,
|
||||
falsingManager,
|
||||
statusBarKeyguardViewManager,
|
||||
featureFlags
|
||||
statusBarKeyguardViewManager
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -165,26 +165,11 @@ class QSPanelTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTopPadding_notCombinedHeaders() {
|
||||
qsPanel.setUsingCombinedHeaders(false)
|
||||
fun testTopPadding() {
|
||||
val padding = 10
|
||||
val paddingCombined = 100
|
||||
context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
|
||||
context.orCreateTestableResources.addOverride(
|
||||
R.dimen.qs_panel_padding_top_combined_headers, paddingCombined)
|
||||
|
||||
qsPanel.updatePadding()
|
||||
assertThat(qsPanel.paddingTop).isEqualTo(padding)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTopPadding_combinedHeaders() {
|
||||
qsPanel.setUsingCombinedHeaders(true)
|
||||
val padding = 10
|
||||
val paddingCombined = 100
|
||||
context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
|
||||
context.orCreateTestableResources.addOverride(
|
||||
R.dimen.qs_panel_padding_top_combined_headers, paddingCombined)
|
||||
context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, paddingCombined)
|
||||
|
||||
qsPanel.updatePadding()
|
||||
assertThat(qsPanel.paddingTop).isEqualTo(paddingCombined)
|
||||
|
||||
@@ -18,37 +18,16 @@ package com.android.systemui.qs
|
||||
|
||||
import android.content.Context
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.view.View
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.battery.BatteryMeterViewController
|
||||
import com.android.systemui.colorextraction.SysuiColorExtractor
|
||||
import com.android.systemui.demomode.DemoModeController
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
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.Clock
|
||||
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.argumentCaptor
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Answers
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.anyBoolean
|
||||
import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@@ -58,78 +37,21 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
|
||||
@Mock
|
||||
private lateinit var view: QuickStatusBarHeader
|
||||
@Mock
|
||||
private lateinit var privacyIconsController: HeaderPrivacyIconsController
|
||||
@Mock
|
||||
private lateinit var statusBarIconController: StatusBarIconController
|
||||
@Mock
|
||||
private lateinit var demoModeController: DemoModeController
|
||||
@Mock
|
||||
private lateinit var quickQSPanelController: QuickQSPanelController
|
||||
@Mock(answer = Answers.RETURNS_SELF)
|
||||
private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
|
||||
@Mock
|
||||
private lateinit var qsCarrierGroupController: QSCarrierGroupController
|
||||
@Mock
|
||||
private lateinit var colorExtractor: SysuiColorExtractor
|
||||
@Mock
|
||||
private lateinit var iconContainer: StatusIconContainer
|
||||
@Mock
|
||||
private lateinit var qsCarrierGroup: QSCarrierGroup
|
||||
@Mock
|
||||
private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
|
||||
@Mock
|
||||
private lateinit var variableDateViewController: VariableDateViewController
|
||||
@Mock
|
||||
private lateinit var batteryMeterViewController: BatteryMeterViewController
|
||||
@Mock
|
||||
private lateinit var clock: Clock
|
||||
@Mock
|
||||
private lateinit var variableDateView: VariableDateView
|
||||
@Mock
|
||||
private lateinit var mockView: View
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private lateinit var context: Context
|
||||
@Mock
|
||||
private lateinit var featureFlags: FeatureFlags
|
||||
@Mock
|
||||
private lateinit var insetsProvider: StatusBarContentInsetsProvider
|
||||
@Mock
|
||||
private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory
|
||||
@Mock
|
||||
private lateinit var iconManager: StatusBarIconController.TintedIconManager
|
||||
|
||||
private val qsExpansionPathInterpolator = QSExpansionPathInterpolator()
|
||||
|
||||
private lateinit var controller: QuickStatusBarHeaderController
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
stubViews()
|
||||
`when`(iconContainer.context).thenReturn(context)
|
||||
`when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
|
||||
`when`(variableDateViewControllerFactory.create(any()))
|
||||
.thenReturn(variableDateViewController)
|
||||
`when`(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
|
||||
`when`(view.resources).thenReturn(mContext.resources)
|
||||
`when`(view.isAttachedToWindow).thenReturn(true)
|
||||
`when`(view.context).thenReturn(context)
|
||||
|
||||
controller = QuickStatusBarHeaderController(
|
||||
view,
|
||||
privacyIconsController,
|
||||
statusBarIconController,
|
||||
demoModeController,
|
||||
quickQSPanelController,
|
||||
qsCarrierGroupControllerBuilder,
|
||||
colorExtractor,
|
||||
qsExpansionPathInterpolator,
|
||||
featureFlags,
|
||||
variableDateViewControllerFactory,
|
||||
batteryMeterViewController,
|
||||
insetsProvider,
|
||||
iconManagerFactory,
|
||||
)
|
||||
controller = QuickStatusBarHeaderController(view, quickQSPanelController)
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -138,74 +60,11 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClockNotClickable() {
|
||||
assertThat(clock.isClickable).isFalse()
|
||||
}
|
||||
fun testListeningStatus() {
|
||||
controller.setListening(true)
|
||||
verify(quickQSPanelController).setListening(true)
|
||||
|
||||
@Test
|
||||
fun testSingleCarrierListenerAttachedOnInit() {
|
||||
controller.init()
|
||||
|
||||
verify(qsCarrierGroupController).setOnSingleCarrierChangedListener(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSingleCarrierSetOnViewOnInit_false() {
|
||||
`when`(qsCarrierGroupController.isSingleCarrier).thenReturn(false)
|
||||
controller.init()
|
||||
|
||||
verify(view).setIsSingleCarrier(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSingleCarrierSetOnViewOnInit_true() {
|
||||
`when`(qsCarrierGroupController.isSingleCarrier).thenReturn(true)
|
||||
controller.init()
|
||||
|
||||
verify(view).setIsSingleCarrier(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRSSISlot_notCombined() {
|
||||
controller.init()
|
||||
|
||||
val captor = argumentCaptor<List<String>>()
|
||||
verify(view).onAttach(any(), any(), capture(captor), any(), anyBoolean())
|
||||
|
||||
assertThat(captor.value).containsExactly(
|
||||
mContext.getString(com.android.internal.R.string.status_bar_mobile)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSingleCarrierCallback() {
|
||||
controller.init()
|
||||
reset(view)
|
||||
|
||||
val captor = argumentCaptor<QSCarrierGroupController.OnSingleCarrierChangedListener>()
|
||||
verify(qsCarrierGroupController).setOnSingleCarrierChangedListener(capture(captor))
|
||||
|
||||
captor.value.onSingleCarrierChanged(true)
|
||||
verify(view).setIsSingleCarrier(true)
|
||||
|
||||
captor.value.onSingleCarrierChanged(false)
|
||||
verify(view).setIsSingleCarrier(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAlarmIconIgnored() {
|
||||
controller.init()
|
||||
|
||||
verify(iconContainer).addIgnoredSlot(
|
||||
mContext.getString(com.android.internal.R.string.status_bar_alarm_clock))
|
||||
}
|
||||
|
||||
private fun stubViews() {
|
||||
`when`(view.findViewById<View>(anyInt())).thenReturn(mockView)
|
||||
`when`(view.findViewById<QSCarrierGroup>(R.id.carrier_group)).thenReturn(qsCarrierGroup)
|
||||
`when`(view.findViewById<StatusIconContainer>(R.id.statusIcons)).thenReturn(iconContainer)
|
||||
`when`(view.findViewById<Clock>(R.id.clock)).thenReturn(clock)
|
||||
`when`(view.requireViewById<VariableDateView>(R.id.date)).thenReturn(variableDateView)
|
||||
`when`(view.requireViewById<VariableDateView>(R.id.date_clock)).thenReturn(variableDateView)
|
||||
controller.setListening(false)
|
||||
verify(quickQSPanelController).setListening(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,319 +0,0 @@
|
||||
package com.android.systemui.shade
|
||||
|
||||
import android.animation.Animator
|
||||
import android.app.StatusBarManager
|
||||
import android.content.Context
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.view.View
|
||||
import android.view.ViewPropertyAnimator
|
||||
import android.widget.TextView
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.animation.Interpolators
|
||||
import com.android.systemui.animation.ShadeInterpolation
|
||||
import com.android.systemui.battery.BatteryMeterView
|
||||
import com.android.systemui.battery.BatteryMeterViewController
|
||||
import com.android.systemui.demomode.DemoMode
|
||||
import com.android.systemui.demomode.DemoModeController
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
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.Clock
|
||||
import com.android.systemui.statusbar.policy.FakeConfigurationController
|
||||
import com.android.systemui.statusbar.policy.VariableDateViewController
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.argumentCaptor
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.android.systemui.util.mockito.mock
|
||||
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.Answers
|
||||
import org.mockito.ArgumentMatchers.anyFloat
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.verifyZeroInteractions
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var view: View
|
||||
@Mock private lateinit var statusIcons: StatusIconContainer
|
||||
@Mock private lateinit var statusBarIconController: StatusBarIconController
|
||||
@Mock private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory
|
||||
@Mock private lateinit var iconManager: StatusBarIconController.TintedIconManager
|
||||
@Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController
|
||||
@Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
|
||||
@Mock private lateinit var featureFlags: FeatureFlags
|
||||
@Mock private lateinit var clock: Clock
|
||||
@Mock private lateinit var date: TextView
|
||||
@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 private lateinit var demoModeController: DemoModeController
|
||||
@Mock private lateinit var qsBatteryModeController: QsBatteryModeController
|
||||
|
||||
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
|
||||
var viewVisibility = View.GONE
|
||||
var viewAlpha = 1f
|
||||
|
||||
private lateinit var mLargeScreenShadeHeaderController: LargeScreenShadeHeaderController
|
||||
private lateinit var carrierIconSlots: List<String>
|
||||
private val configurationController = FakeConfigurationController()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
whenever<Clock>(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<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(view.context).thenReturn(context)
|
||||
whenever(view.resources).thenReturn(context.resources)
|
||||
whenever(statusIcons.context).thenReturn(context)
|
||||
whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any()))
|
||||
.thenReturn(qsCarrierGroupControllerBuilder)
|
||||
whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
|
||||
whenever(view.setVisibility(anyInt())).then {
|
||||
viewVisibility = it.arguments[0] as Int
|
||||
null
|
||||
}
|
||||
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
|
||||
|
||||
whenever(view.setAlpha(anyFloat())).then {
|
||||
viewAlpha = it.arguments[0] as Float
|
||||
null
|
||||
}
|
||||
whenever(view.alpha).thenAnswer { _ -> viewAlpha }
|
||||
|
||||
whenever(variableDateViewControllerFactory.create(any()))
|
||||
.thenReturn(variableDateViewController)
|
||||
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
|
||||
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false)
|
||||
mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController(
|
||||
view,
|
||||
statusBarIconController,
|
||||
iconManagerFactory,
|
||||
privacyIconsController,
|
||||
insetsProvider,
|
||||
configurationController,
|
||||
variableDateViewControllerFactory,
|
||||
batteryMeterViewController,
|
||||
dumpManager,
|
||||
featureFlags,
|
||||
qsCarrierGroupControllerBuilder,
|
||||
combinedShadeHeadersConstraintManager,
|
||||
demoModeController,
|
||||
qsBatteryModeController,
|
||||
)
|
||||
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.largeScreenActive = false
|
||||
assertThat(viewVisibility).isEqualTo(View.GONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateListeners_registersWhenVisible() {
|
||||
makeShadeVisible()
|
||||
verify(qsCarrierGroupController).setListening(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun statusIconsAddedWhenAttached() {
|
||||
verify(statusBarIconController).addIconGroup(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun statusIconsRemovedWhenDettached() {
|
||||
mLargeScreenShadeHeaderController.simulateViewDetached()
|
||||
verify(statusBarIconController).removeIconGroup(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shadeExpandedFraction_updatesAlpha() {
|
||||
makeShadeVisible()
|
||||
mLargeScreenShadeHeaderController.shadeExpandedFraction = 0.5f
|
||||
verify(view).setAlpha(ShadeInterpolation.getContentAlpha(0.5f))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singleCarrier_enablesCarrierIconsInStatusIcons() {
|
||||
whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true)
|
||||
|
||||
makeShadeVisible()
|
||||
|
||||
verify(statusIcons).removeIgnoredSlots(carrierIconSlots)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dualCarrier_disablesCarrierIconsInStatusIcons() {
|
||||
whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(false)
|
||||
|
||||
makeShadeVisible()
|
||||
|
||||
verify(statusIcons).addIgnoredSlots(carrierIconSlots)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun disableQS_notDisabled_visible() {
|
||||
makeShadeVisible()
|
||||
mLargeScreenShadeHeaderController.disable(0, 0, false)
|
||||
|
||||
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun disableQS_disabled_gone() {
|
||||
makeShadeVisible()
|
||||
mLargeScreenShadeHeaderController.disable(0, StatusBarManager.DISABLE2_QUICK_SETTINGS,
|
||||
false)
|
||||
|
||||
assertThat(viewVisibility).isEqualTo(View.GONE)
|
||||
}
|
||||
|
||||
private fun makeShadeVisible() {
|
||||
mLargeScreenShadeHeaderController.largeScreenActive = true
|
||||
mLargeScreenShadeHeaderController.qsVisible = true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateConfig_changesFontStyle() {
|
||||
configurationController.notifyDensityOrFontScaleChanged()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun alarmIconIgnored() {
|
||||
verify(statusIcons).addIgnoredSlot(
|
||||
context.getString(com.android.internal.R.string.status_bar_alarm_clock)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateOutOnStartCustomizing() {
|
||||
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
val duration = 1000L
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
|
||||
mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, duration)
|
||||
|
||||
verify(animator).setDuration(duration)
|
||||
verify(animator).alpha(0f)
|
||||
verify(animator).setInterpolator(Interpolators.ALPHA_OUT)
|
||||
verify(animator).start()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateInOnEndCustomizing() {
|
||||
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
val duration = 1000L
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
|
||||
mLargeScreenShadeHeaderController.startCustomizingAnimation(show = false, duration)
|
||||
|
||||
verify(animator).setDuration(duration)
|
||||
verify(animator).alpha(1f)
|
||||
verify(animator).setInterpolator(Interpolators.ALPHA_IN)
|
||||
verify(animator).start()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun customizerAnimatorChangesViewVisibility() {
|
||||
makeShadeVisible()
|
||||
|
||||
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
val duration = 1000L
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
val listenerCaptor = argumentCaptor<Animator.AnimatorListener>()
|
||||
|
||||
mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, duration)
|
||||
verify(animator).setListener(capture(listenerCaptor))
|
||||
// Start and end the animation
|
||||
listenerCaptor.value.onAnimationStart(mock())
|
||||
listenerCaptor.value.onAnimationEnd(mock())
|
||||
assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
|
||||
|
||||
reset(animator)
|
||||
mLargeScreenShadeHeaderController.startCustomizingAnimation(show = false, duration)
|
||||
verify(animator).setListener(capture(listenerCaptor))
|
||||
// Start and end the animation
|
||||
listenerCaptor.value.onAnimationStart(mock())
|
||||
listenerCaptor.value.onAnimationEnd(mock())
|
||||
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animatorListenerClearedAtEnd() {
|
||||
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
|
||||
mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, 0L)
|
||||
val listenerCaptor = argumentCaptor<Animator.AnimatorListener>()
|
||||
verify(animator).setListener(capture(listenerCaptor))
|
||||
|
||||
listenerCaptor.value.onAnimationEnd(mock())
|
||||
verify(animator).setListener(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun demoMode_attachDemoMode() {
|
||||
val cb = argumentCaptor<DemoMode>()
|
||||
verify(demoModeController).addCallback(capture(cb))
|
||||
cb.value.onDemoModeStarted()
|
||||
verify(clock).onDemoModeStarted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun demoMode_detachDemoMode() {
|
||||
mLargeScreenShadeHeaderController.simulateViewDetached()
|
||||
val cb = argumentCaptor<DemoMode>()
|
||||
verify(demoModeController).removeCallback(capture(cb))
|
||||
cb.value.onDemoModeFinished()
|
||||
verify(clock).onDemoModeFinished()
|
||||
}
|
||||
}
|
||||
@@ -272,7 +272,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
|
||||
@Mock private NavigationModeController mNavigationModeController;
|
||||
@Mock private NavigationBarController mNavigationBarController;
|
||||
@Mock private QuickSettingsController mQsController;
|
||||
@Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
|
||||
@Mock private ShadeHeaderController mShadeHeaderController;
|
||||
@Mock private ContentResolver mContentResolver;
|
||||
@Mock private TapAgainViewController mTapAgainViewController;
|
||||
@Mock private KeyguardIndicationController mKeyguardIndicationController;
|
||||
@@ -516,7 +516,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
|
||||
mFragmentService,
|
||||
mContentResolver,
|
||||
mRecordingController,
|
||||
mLargeScreenShadeHeaderController,
|
||||
mShadeHeaderController,
|
||||
mScreenOffAnimationController,
|
||||
mLockscreenGestureLogger,
|
||||
mShadeExpansionStateManager,
|
||||
@@ -592,7 +592,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
|
||||
mNotificationStackScrollLayoutController,
|
||||
mLockscreenShadeTransitionController,
|
||||
mNotificationShadeDepthController,
|
||||
mLargeScreenShadeHeaderController,
|
||||
mShadeHeaderController,
|
||||
mStatusBarTouchableRegionManager,
|
||||
mKeyguardStateController,
|
||||
mKeyguardBypassController,
|
||||
|
||||
@@ -12,7 +12,6 @@ import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.fragments.FragmentHostManager
|
||||
import com.android.systemui.fragments.FragmentService
|
||||
import com.android.systemui.navigationbar.NavigationModeController
|
||||
@@ -40,8 +39,8 @@ import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@@ -68,12 +67,10 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
@Mock
|
||||
private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer
|
||||
@Mock
|
||||
private lateinit var largeScreenShadeHeaderController: LargeScreenShadeHeaderController
|
||||
private lateinit var mShadeHeaderController: ShadeHeaderController
|
||||
@Mock
|
||||
private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
|
||||
@Mock
|
||||
private lateinit var featureFlags: FeatureFlags
|
||||
@Mock
|
||||
private lateinit var fragmentService: FragmentService
|
||||
@Mock
|
||||
private lateinit var fragmentHostManager: FragmentHostManager
|
||||
@@ -109,9 +106,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
notificationsQSContainer,
|
||||
navigationModeController,
|
||||
overviewProxyService,
|
||||
largeScreenShadeHeaderController,
|
||||
mShadeHeaderController,
|
||||
shadeExpansionStateManager,
|
||||
featureFlags,
|
||||
fragmentService,
|
||||
delayableExecutor
|
||||
)
|
||||
@@ -396,9 +392,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
container,
|
||||
navigationModeController,
|
||||
overviewProxyService,
|
||||
largeScreenShadeHeaderController,
|
||||
mShadeHeaderController,
|
||||
shadeExpansionStateManager,
|
||||
featureFlags,
|
||||
fragmentService,
|
||||
delayableExecutor
|
||||
)
|
||||
@@ -429,16 +424,16 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun testStartCustomizingWithDuration() {
|
||||
controller.setCustomizerShowing(true, 100L)
|
||||
verify(largeScreenShadeHeaderController).startCustomizingAnimation(true, 100L)
|
||||
verify(mShadeHeaderController).startCustomizingAnimation(true, 100L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEndCustomizingWithDuration() {
|
||||
controller.setCustomizerShowing(true, 0L) // Only tracks changes
|
||||
reset(largeScreenShadeHeaderController)
|
||||
reset(mShadeHeaderController)
|
||||
|
||||
controller.setCustomizerShowing(false, 100L)
|
||||
verify(largeScreenShadeHeaderController).startCustomizingAnimation(false, 100L)
|
||||
verify(mShadeHeaderController).startCustomizingAnimation(false, 100L)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -119,7 +119,7 @@ public class QuickSettingsControllerTest extends SysuiTestCase {
|
||||
@Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
|
||||
@Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
|
||||
@Mock private NotificationShadeDepthController mNotificationShadeDepthController;
|
||||
@Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
|
||||
@Mock private ShadeHeaderController mShadeHeaderController;
|
||||
@Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
|
||||
@Mock private KeyguardStateController mKeyguardStateController;
|
||||
@Mock private KeyguardBypassController mKeyguardBypassController;
|
||||
@@ -204,7 +204,7 @@ public class QuickSettingsControllerTest extends SysuiTestCase {
|
||||
mNotificationStackScrollLayoutController,
|
||||
mLockscreenShadeTransitionController,
|
||||
mNotificationShadeDepthController,
|
||||
mLargeScreenShadeHeaderController,
|
||||
mShadeHeaderController,
|
||||
mStatusBarTouchableRegionManager,
|
||||
mKeyguardStateController,
|
||||
mKeyguardBypassController,
|
||||
@@ -259,11 +259,11 @@ public class QuickSettingsControllerTest extends SysuiTestCase {
|
||||
mStatusBarStateController.setState(SHADE);
|
||||
when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(true);
|
||||
mQsController.updateResources();
|
||||
verify(mLargeScreenShadeHeaderController).setLargeScreenActive(true);
|
||||
verify(mShadeHeaderController).setLargeScreenActive(true);
|
||||
|
||||
when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(false);
|
||||
mQsController.updateResources();
|
||||
verify(mLargeScreenShadeHeaderController).setLargeScreenActive(false);
|
||||
verify(mShadeHeaderController).setLargeScreenActive(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.shade
|
||||
|
||||
import android.animation.Animator
|
||||
import android.app.StatusBarManager
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.content.res.XmlResourceParser
|
||||
@@ -39,16 +40,13 @@ import com.android.systemui.battery.BatteryMeterViewController
|
||||
import com.android.systemui.demomode.DemoMode
|
||||
import com.android.systemui.demomode.DemoModeController
|
||||
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.QQS_HEADER_CONSTRAINT
|
||||
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
|
||||
import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
|
||||
import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
|
||||
import com.android.systemui.shade.ShadeHeaderController.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
|
||||
@@ -68,136 +66,106 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Answers
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.ArgumentMatchers.anyFloat
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
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.mock
|
||||
import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.same
|
||||
import org.mockito.Mockito.spy
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
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() {
|
||||
class ShadeHeaderControllerTest : SysuiTestCase() {
|
||||
|
||||
@Mock
|
||||
private lateinit var statusIcons: StatusIconContainer
|
||||
@Mock
|
||||
private lateinit var statusBarIconController: StatusBarIconController
|
||||
@Mock
|
||||
private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory
|
||||
@Mock
|
||||
private lateinit var iconManager: StatusBarIconController.TintedIconManager
|
||||
@Mock
|
||||
private lateinit var qsCarrierGroupController: QSCarrierGroupController
|
||||
@Mock
|
||||
private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
|
||||
@Mock
|
||||
private lateinit var featureFlags: FeatureFlags
|
||||
@Mock
|
||||
private lateinit var clock: Clock
|
||||
@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(answer = Answers.RETURNS_MOCKS) private lateinit var view: MotionLayout
|
||||
@Mock private lateinit var statusIcons: StatusIconContainer
|
||||
@Mock private lateinit var statusBarIconController: StatusBarIconController
|
||||
@Mock private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory
|
||||
@Mock private lateinit var iconManager: StatusBarIconController.TintedIconManager
|
||||
@Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController
|
||||
@Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
|
||||
@Mock private lateinit var clock: Clock
|
||||
@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 private lateinit var mockedContext: Context
|
||||
private lateinit var viewContext: 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
|
||||
@Mock private lateinit var qqsConstraints: ConstraintSet
|
||||
@Mock private lateinit var qsConstraints: ConstraintSet
|
||||
@Mock private lateinit var largeScreenConstraints: ConstraintSet
|
||||
|
||||
@Mock private lateinit var demoModeController: DemoModeController
|
||||
@Mock private lateinit var qsBatteryModeController: QsBatteryModeController
|
||||
|
||||
@JvmField @Rule
|
||||
val mockitoRule = MockitoJUnit.rule()
|
||||
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
|
||||
var viewVisibility = View.GONE
|
||||
var viewAlpha = 1f
|
||||
|
||||
private lateinit var controller: LargeScreenShadeHeaderController
|
||||
private lateinit var shadeHeaderController: ShadeHeaderController
|
||||
private lateinit var carrierIconSlots: List<String>
|
||||
private val configurationController = FakeConfigurationController()
|
||||
private lateinit var demoModeControllerCapture: ArgumentCaptor<DemoMode>
|
||||
@Captor private lateinit var demoModeControllerCapture: ArgumentCaptor<DemoMode>
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
demoModeControllerCapture = argumentCaptor<DemoMode>()
|
||||
fun setup() {
|
||||
whenever<Clock>(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)
|
||||
|
||||
viewContext = Mockito.spy(context)
|
||||
whenever(view.context).thenReturn(viewContext)
|
||||
whenever(view.resources).thenReturn(context.resources)
|
||||
whenever(statusIcons.context).thenReturn(context)
|
||||
whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any()))
|
||||
.thenReturn(qsCarrierGroupControllerBuilder)
|
||||
whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
|
||||
|
||||
viewContext = spy(context)
|
||||
whenever(view.context).thenReturn(viewContext)
|
||||
whenever(view.resources).thenReturn(context.resources)
|
||||
whenever(view.setVisibility(ArgumentMatchers.anyInt())).then {
|
||||
whenever(view.setVisibility(anyInt())).then {
|
||||
viewVisibility = it.arguments[0] as Int
|
||||
null
|
||||
}
|
||||
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
|
||||
whenever(view.alpha).thenReturn(1f)
|
||||
|
||||
whenever(view.setAlpha(anyFloat())).then {
|
||||
viewAlpha = it.arguments[0] as Float
|
||||
null
|
||||
}
|
||||
whenever(view.alpha).thenAnswer { _ -> viewAlpha }
|
||||
|
||||
whenever(variableDateViewControllerFactory.create(any()))
|
||||
.thenReturn(variableDateViewController)
|
||||
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
|
||||
|
||||
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
|
||||
|
||||
setUpDefaultInsets()
|
||||
setUpMotionLayout(view)
|
||||
|
||||
controller = LargeScreenShadeHeaderController(
|
||||
shadeHeaderController =
|
||||
ShadeHeaderController(
|
||||
view,
|
||||
statusBarIconController,
|
||||
iconManagerFactory,
|
||||
@@ -207,16 +175,171 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
variableDateViewControllerFactory,
|
||||
batteryMeterViewController,
|
||||
dumpManager,
|
||||
featureFlags,
|
||||
qsCarrierGroupControllerBuilder,
|
||||
combinedShadeHeadersConstraintManager,
|
||||
demoModeController,
|
||||
qsBatteryModeController,
|
||||
)
|
||||
)
|
||||
whenever(view.isAttachedToWindow).thenReturn(true)
|
||||
controller.init()
|
||||
carrierIconSlots = listOf(
|
||||
context.getString(com.android.internal.R.string.status_bar_mobile))
|
||||
shadeHeaderController.init()
|
||||
carrierIconSlots =
|
||||
listOf(context.getString(com.android.internal.R.string.status_bar_mobile))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateListeners_registersWhenVisible() {
|
||||
makeShadeVisible()
|
||||
verify(qsCarrierGroupController).setListening(true)
|
||||
verify(statusBarIconController).addIconGroup(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun statusIconsAddedWhenAttached() {
|
||||
verify(statusBarIconController).addIconGroup(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun statusIconsRemovedWhenDettached() {
|
||||
shadeHeaderController.simulateViewDetached()
|
||||
verify(statusBarIconController).removeIconGroup(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shadeExpandedFraction_updatesAlpha() {
|
||||
makeShadeVisible()
|
||||
shadeHeaderController.shadeExpandedFraction = 0.5f
|
||||
verify(view).setAlpha(ShadeInterpolation.getContentAlpha(0.5f))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singleCarrier_enablesCarrierIconsInStatusIcons() {
|
||||
whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true)
|
||||
|
||||
makeShadeVisible()
|
||||
|
||||
verify(statusIcons).removeIgnoredSlots(carrierIconSlots)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dualCarrier_disablesCarrierIconsInStatusIcons() {
|
||||
whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(false)
|
||||
|
||||
makeShadeVisible()
|
||||
|
||||
verify(statusIcons).addIgnoredSlots(carrierIconSlots)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun disableQS_notDisabled_visible() {
|
||||
makeShadeVisible()
|
||||
shadeHeaderController.disable(0, 0, false)
|
||||
|
||||
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun disableQS_disabled_gone() {
|
||||
makeShadeVisible()
|
||||
shadeHeaderController.disable(0, StatusBarManager.DISABLE2_QUICK_SETTINGS, false)
|
||||
|
||||
assertThat(viewVisibility).isEqualTo(View.GONE)
|
||||
}
|
||||
|
||||
private fun makeShadeVisible() {
|
||||
shadeHeaderController.largeScreenActive = true
|
||||
shadeHeaderController.qsVisible = true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateConfig_changesFontStyle() {
|
||||
configurationController.notifyDensityOrFontScaleChanged()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateOutOnStartCustomizing() {
|
||||
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
val duration = 1000L
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
|
||||
shadeHeaderController.startCustomizingAnimation(show = true, duration)
|
||||
|
||||
verify(animator).setDuration(duration)
|
||||
verify(animator).alpha(0f)
|
||||
verify(animator).setInterpolator(Interpolators.ALPHA_OUT)
|
||||
verify(animator).start()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateInOnEndCustomizing() {
|
||||
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
val duration = 1000L
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
|
||||
shadeHeaderController.startCustomizingAnimation(show = false, duration)
|
||||
|
||||
verify(animator).setDuration(duration)
|
||||
verify(animator).alpha(1f)
|
||||
verify(animator).setInterpolator(Interpolators.ALPHA_IN)
|
||||
verify(animator).start()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun customizerAnimatorChangesViewVisibility() {
|
||||
makeShadeVisible()
|
||||
|
||||
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
val duration = 1000L
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
val listenerCaptor = argumentCaptor<Animator.AnimatorListener>()
|
||||
|
||||
shadeHeaderController.startCustomizingAnimation(show = true, duration)
|
||||
verify(animator).setListener(capture(listenerCaptor))
|
||||
// Start and end the animation
|
||||
listenerCaptor.value.onAnimationStart(mock())
|
||||
listenerCaptor.value.onAnimationEnd(mock())
|
||||
assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
|
||||
|
||||
reset(animator)
|
||||
shadeHeaderController.startCustomizingAnimation(show = false, duration)
|
||||
verify(animator).setListener(capture(listenerCaptor))
|
||||
// Start and end the animation
|
||||
listenerCaptor.value.onAnimationStart(mock())
|
||||
listenerCaptor.value.onAnimationEnd(mock())
|
||||
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animatorListenersClearedAtEnd() {
|
||||
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
|
||||
shadeHeaderController.startCustomizingAnimation(show = true, 0L)
|
||||
val listenerCaptor = argumentCaptor<Animator.AnimatorListener>()
|
||||
verify(animator).setListener(capture(listenerCaptor))
|
||||
|
||||
listenerCaptor.value.onAnimationEnd(mock())
|
||||
verify(animator).setListener(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun demoMode_attachDemoMode() {
|
||||
val cb = argumentCaptor<DemoMode>()
|
||||
verify(demoModeController).addCallback(capture(cb))
|
||||
cb.value.onDemoModeStarted()
|
||||
verify(clock).onDemoModeStarted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun demoMode_detachDemoMode() {
|
||||
shadeHeaderController.simulateViewDetached()
|
||||
val cb = argumentCaptor<DemoMode>()
|
||||
verify(demoModeController).removeCallback(capture(cb))
|
||||
cb.value.onDemoModeFinished()
|
||||
verify(clock).onDemoModeFinished()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -226,23 +349,21 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
verify(batteryMeterViewController).init()
|
||||
verify(batteryMeterViewController).ignoreTunerUpdates()
|
||||
|
||||
val inOrder = inOrder(qsCarrierGroupControllerBuilder)
|
||||
val inOrder = Mockito.inOrder(qsCarrierGroupControllerBuilder)
|
||||
inOrder.verify(qsCarrierGroupControllerBuilder).setQSCarrierGroup(carrierGroup)
|
||||
inOrder.verify(qsCarrierGroupControllerBuilder).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `battery mode controller called when qsExpandedFraction changes`() {
|
||||
whenever(qsBatteryModeController.getBatteryMode(same(null), eq(0f)))
|
||||
.thenReturn(BatteryMeterView.MODE_ON)
|
||||
whenever(qsBatteryModeController.getBatteryMode(same(null), eq(1f)))
|
||||
.thenReturn(BatteryMeterView.MODE_ESTIMATE)
|
||||
controller.qsVisible = true
|
||||
whenever(qsBatteryModeController.getBatteryMode(Mockito.same(null), eq(0f)))
|
||||
.thenReturn(BatteryMeterView.MODE_ON)
|
||||
whenever(qsBatteryModeController.getBatteryMode(Mockito.same(null), eq(1f)))
|
||||
.thenReturn(BatteryMeterView.MODE_ESTIMATE)
|
||||
shadeHeaderController.qsVisible = true
|
||||
|
||||
val times = 10
|
||||
repeat(times) {
|
||||
controller.qsExpandedFraction = it / (times - 1).toFloat()
|
||||
}
|
||||
repeat(times) { shadeHeaderController.qsExpandedFraction = it / (times - 1).toFloat() }
|
||||
|
||||
verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ON)
|
||||
verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
|
||||
@@ -277,89 +398,89 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun testShadeExpanded_true() {
|
||||
// When shade is expanded, view should be visible regardless of largeScreenActive
|
||||
controller.largeScreenActive = false
|
||||
controller.qsVisible = true
|
||||
shadeHeaderController.largeScreenActive = false
|
||||
shadeHeaderController.qsVisible = true
|
||||
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
|
||||
|
||||
controller.largeScreenActive = true
|
||||
shadeHeaderController.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
|
||||
shadeHeaderController.largeScreenActive = false
|
||||
shadeHeaderController.qsVisible = false
|
||||
assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
|
||||
|
||||
controller.largeScreenActive = true
|
||||
shadeHeaderController.largeScreenActive = true
|
||||
assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLargeScreenActive_false() {
|
||||
controller.largeScreenActive = true // Make sure there's a change
|
||||
clearInvocations(view)
|
||||
shadeHeaderController.largeScreenActive = true // Make sure there's a change
|
||||
Mockito.clearInvocations(view)
|
||||
|
||||
controller.largeScreenActive = false
|
||||
shadeHeaderController.largeScreenActive = false
|
||||
|
||||
verify(view).setTransition(HEADER_TRANSITION_ID)
|
||||
verify(view).setTransition(ShadeHeaderController.HEADER_TRANSITION_ID)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShadeExpandedFraction() {
|
||||
// View needs to be visible for this to actually take effect
|
||||
controller.qsVisible = true
|
||||
shadeHeaderController.qsVisible = true
|
||||
|
||||
clearInvocations(view)
|
||||
controller.shadeExpandedFraction = 0.3f
|
||||
Mockito.clearInvocations(view)
|
||||
shadeHeaderController.shadeExpandedFraction = 0.3f
|
||||
verify(view).alpha = ShadeInterpolation.getContentAlpha(0.3f)
|
||||
|
||||
clearInvocations(view)
|
||||
controller.shadeExpandedFraction = 1f
|
||||
Mockito.clearInvocations(view)
|
||||
shadeHeaderController.shadeExpandedFraction = 1f
|
||||
verify(view).alpha = ShadeInterpolation.getContentAlpha(1f)
|
||||
|
||||
clearInvocations(view)
|
||||
controller.shadeExpandedFraction = 0f
|
||||
Mockito.clearInvocations(view)
|
||||
shadeHeaderController.shadeExpandedFraction = 0f
|
||||
verify(view).alpha = ShadeInterpolation.getContentAlpha(0f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testQsExpandedFraction_headerTransition() {
|
||||
controller.qsVisible = true
|
||||
controller.largeScreenActive = false
|
||||
shadeHeaderController.qsVisible = true
|
||||
shadeHeaderController.largeScreenActive = false
|
||||
|
||||
clearInvocations(view)
|
||||
controller.qsExpandedFraction = 0.3f
|
||||
Mockito.clearInvocations(view)
|
||||
shadeHeaderController.qsExpandedFraction = 0.3f
|
||||
verify(view).progress = 0.3f
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testQsExpandedFraction_largeScreen() {
|
||||
controller.qsVisible = true
|
||||
controller.largeScreenActive = true
|
||||
shadeHeaderController.qsVisible = true
|
||||
shadeHeaderController.largeScreenActive = true
|
||||
|
||||
clearInvocations(view)
|
||||
controller.qsExpandedFraction = 0.3f
|
||||
verify(view, never()).progress = anyFloat()
|
||||
Mockito.clearInvocations(view)
|
||||
shadeHeaderController.qsExpandedFraction = 0.3f
|
||||
verify(view, Mockito.never()).progress = anyFloat()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScrollY_headerTransition() {
|
||||
controller.largeScreenActive = false
|
||||
shadeHeaderController.largeScreenActive = false
|
||||
|
||||
clearInvocations(view)
|
||||
controller.qsScrollY = 20
|
||||
Mockito.clearInvocations(view)
|
||||
shadeHeaderController.qsScrollY = 20
|
||||
verify(view).scrollY = 20
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScrollY_largeScreen() {
|
||||
controller.largeScreenActive = true
|
||||
shadeHeaderController.largeScreenActive = true
|
||||
|
||||
clearInvocations(view)
|
||||
controller.qsScrollY = 20
|
||||
verify(view, never()).scrollY = anyInt()
|
||||
Mockito.clearInvocations(view)
|
||||
shadeHeaderController.qsScrollY = 20
|
||||
verify(view, Mockito.never()).scrollY = anyInt()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -381,9 +502,9 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
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())
|
||||
verify(chipNotVisibleChanges.qqsConstraintsChanges, Mockito.never())!!.invoke(any())
|
||||
verify(chipNotVisibleChanges.qsConstraintsChanges, Mockito.never())!!.invoke(any())
|
||||
verify(chipNotVisibleChanges.largeScreenConstraintsChanges, Mockito.never())!!.invoke(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -401,10 +522,11 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
|
||||
captor.value.onChipVisibilityRefreshed(false)
|
||||
|
||||
verify(chipVisibleChanges.qqsConstraintsChanges, never())!!.invoke(qqsConstraints)
|
||||
verify(chipVisibleChanges.qsConstraintsChanges, never())!!.invoke(qsConstraints)
|
||||
verify(chipVisibleChanges.largeScreenConstraintsChanges, never())!!
|
||||
.invoke(largeScreenConstraints)
|
||||
verify(chipVisibleChanges.qqsConstraintsChanges, Mockito.never())!!.invoke(qqsConstraints)
|
||||
verify(chipVisibleChanges.qsConstraintsChanges, Mockito.never())!!.invoke(qsConstraints)
|
||||
verify(chipVisibleChanges.largeScreenConstraintsChanges, Mockito.never())!!.invoke(
|
||||
largeScreenConstraints
|
||||
)
|
||||
|
||||
verify(chipNotVisibleChanges.qqsConstraintsChanges)!!.invoke(any())
|
||||
verify(chipNotVisibleChanges.qsConstraintsChanges)!!.invoke(any())
|
||||
@@ -425,9 +547,15 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
|
||||
mockInsetsProvider(insetLeft to insetRight, false)
|
||||
|
||||
whenever(combinedShadeHeadersConstraintManager
|
||||
.edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
|
||||
).thenReturn(mockConstraintsChanges)
|
||||
whenever(
|
||||
combinedShadeHeadersConstraintManager.edgesGuidelinesConstraints(
|
||||
anyInt(),
|
||||
anyInt(),
|
||||
anyInt(),
|
||||
anyInt()
|
||||
)
|
||||
)
|
||||
.thenReturn(mockConstraintsChanges)
|
||||
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets())
|
||||
|
||||
@@ -453,9 +581,15 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
|
||||
mockInsetsProvider(insetLeft to insetRight, false)
|
||||
|
||||
whenever(combinedShadeHeadersConstraintManager
|
||||
.edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
|
||||
).thenReturn(mockConstraintsChanges)
|
||||
whenever(
|
||||
combinedShadeHeadersConstraintManager.edgesGuidelinesConstraints(
|
||||
anyInt(),
|
||||
anyInt(),
|
||||
anyInt(),
|
||||
anyInt()
|
||||
)
|
||||
)
|
||||
.thenReturn(mockConstraintsChanges)
|
||||
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets())
|
||||
|
||||
@@ -479,8 +613,8 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets(null))
|
||||
|
||||
verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
|
||||
verify(combinedShadeHeadersConstraintManager, never())
|
||||
.centerCutoutConstraints(anyBoolean(), anyInt())
|
||||
verify(combinedShadeHeadersConstraintManager, Mockito.never())
|
||||
.centerCutoutConstraints(Mockito.anyBoolean(), anyInt())
|
||||
|
||||
verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
|
||||
@@ -499,8 +633,8 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets())
|
||||
|
||||
verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
|
||||
verify(combinedShadeHeadersConstraintManager, never())
|
||||
.centerCutoutConstraints(anyBoolean(), anyInt())
|
||||
verify(combinedShadeHeadersConstraintManager, Mockito.never())
|
||||
.centerCutoutConstraints(Mockito.anyBoolean(), anyInt())
|
||||
|
||||
verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
|
||||
@@ -521,8 +655,8 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets())
|
||||
|
||||
verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
|
||||
verify(combinedShadeHeadersConstraintManager, never())
|
||||
.centerCutoutConstraints(anyBoolean(), anyInt())
|
||||
verify(combinedShadeHeadersConstraintManager, Mockito.never())
|
||||
.centerCutoutConstraints(Mockito.anyBoolean(), anyInt())
|
||||
|
||||
verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
|
||||
@@ -543,8 +677,8 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(1, 2, 3, 4)))
|
||||
|
||||
verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
|
||||
verify(combinedShadeHeadersConstraintManager, never())
|
||||
.centerCutoutConstraints(anyBoolean(), anyInt())
|
||||
verify(combinedShadeHeadersConstraintManager, Mockito.never())
|
||||
.centerCutoutConstraints(Mockito.anyBoolean(), anyInt())
|
||||
|
||||
verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
|
||||
verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
|
||||
@@ -569,13 +703,17 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
|
||||
mockInsetsProvider(0 to 0, false)
|
||||
|
||||
whenever(combinedShadeHeadersConstraintManager
|
||||
.centerCutoutConstraints(anyBoolean(), anyInt())
|
||||
).thenReturn(mockConstraintsChanges)
|
||||
whenever(
|
||||
combinedShadeHeadersConstraintManager.centerCutoutConstraints(
|
||||
Mockito.anyBoolean(),
|
||||
anyInt()
|
||||
)
|
||||
)
|
||||
.thenReturn(mockConstraintsChanges)
|
||||
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(0, 0, cutoutWidth, 1)))
|
||||
|
||||
verify(combinedShadeHeadersConstraintManager, never()).emptyCutoutConstraints()
|
||||
verify(combinedShadeHeadersConstraintManager, Mockito.never()).emptyCutoutConstraints()
|
||||
val offset = (width - paddingLeft - paddingRight - cutoutWidth) / 2
|
||||
verify(combinedShadeHeadersConstraintManager).centerCutoutConstraints(false, offset)
|
||||
|
||||
@@ -602,13 +740,17 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
|
||||
mockInsetsProvider(0 to 0, false)
|
||||
|
||||
whenever(combinedShadeHeadersConstraintManager
|
||||
.centerCutoutConstraints(anyBoolean(), anyInt())
|
||||
).thenReturn(mockConstraintsChanges)
|
||||
whenever(
|
||||
combinedShadeHeadersConstraintManager.centerCutoutConstraints(
|
||||
Mockito.anyBoolean(),
|
||||
anyInt()
|
||||
)
|
||||
)
|
||||
.thenReturn(mockConstraintsChanges)
|
||||
|
||||
captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(0, 0, cutoutWidth, 1)))
|
||||
|
||||
verify(combinedShadeHeadersConstraintManager, never()).emptyCutoutConstraints()
|
||||
verify(combinedShadeHeadersConstraintManager, Mockito.never()).emptyCutoutConstraints()
|
||||
val offset = (width - paddingLeft - paddingRight - cutoutWidth) / 2
|
||||
verify(combinedShadeHeadersConstraintManager).centerCutoutConstraints(true, offset)
|
||||
|
||||
@@ -619,52 +761,8 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun alarmIconNotIgnored() {
|
||||
verify(statusIcons, never()).addIgnoredSlot(
|
||||
context.getString(com.android.internal.R.string.status_bar_alarm_clock)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun demoMode_attachDemoMode() {
|
||||
verify(demoModeController).addCallback(capture(demoModeControllerCapture))
|
||||
demoModeControllerCapture.value.onDemoModeStarted()
|
||||
verify(clock).onDemoModeStarted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun demoMode_detachDemoMode() {
|
||||
controller.simulateViewDetached()
|
||||
verify(demoModeController).removeCallback(capture(demoModeControllerCapture))
|
||||
demoModeControllerCapture.value.onDemoModeFinished()
|
||||
verify(clock).onDemoModeFinished()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateOutOnStartCustomizing() {
|
||||
val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
val duration = 1000L
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
|
||||
controller.startCustomizingAnimation(show = true, duration)
|
||||
|
||||
verify(animator).setDuration(duration)
|
||||
verify(animator).alpha(0f)
|
||||
verify(animator).setInterpolator(Interpolators.ALPHA_OUT)
|
||||
verify(animator).start()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateInOnEndCustomizing() {
|
||||
val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
val duration = 1000L
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
|
||||
controller.startCustomizingAnimation(show = false, duration)
|
||||
|
||||
verify(animator).setDuration(duration)
|
||||
verify(animator).alpha(1f)
|
||||
verify(animator).setInterpolator(Interpolators.ALPHA_IN)
|
||||
verify(animator).start()
|
||||
verify(statusIcons, Mockito.never())
|
||||
.addIgnoredSlot(context.getString(com.android.internal.R.string.status_bar_alarm_clock))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -674,11 +772,11 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun privacyChipParentVisibleAlways() {
|
||||
controller.largeScreenActive = true
|
||||
controller.largeScreenActive = false
|
||||
controller.largeScreenActive = true
|
||||
shadeHeaderController.largeScreenActive = true
|
||||
shadeHeaderController.largeScreenActive = false
|
||||
shadeHeaderController.largeScreenActive = true
|
||||
|
||||
verify(privacyIconsController, never()).onParentInvisible()
|
||||
verify(privacyIconsController, Mockito.never()).onParentInvisible()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -700,9 +798,9 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
fun onDensityOrFontScaleChanged_reloadConstraints() {
|
||||
// After density or font scale change, constraints need to be reloaded to reflect new
|
||||
// dimensions.
|
||||
reset(qqsConstraints)
|
||||
reset(qsConstraints)
|
||||
reset(largeScreenConstraints)
|
||||
Mockito.reset(qqsConstraints)
|
||||
Mockito.reset(qsConstraints)
|
||||
Mockito.reset(largeScreenConstraints)
|
||||
|
||||
configurationController.notifyDensityOrFontScaleChanged()
|
||||
|
||||
@@ -729,11 +827,11 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
private fun View.executeLayoutChange(
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
listener: View.OnLayoutChangeListener
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
listener: View.OnLayoutChangeListener
|
||||
) {
|
||||
val oldLeft = this.left
|
||||
val oldTop = this.top
|
||||
@@ -746,21 +844,19 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
whenever(this.height).thenReturn(bottom - top)
|
||||
whenever(this.width).thenReturn(right - left)
|
||||
listener.onLayoutChange(
|
||||
this,
|
||||
oldLeft,
|
||||
oldTop,
|
||||
oldRight,
|
||||
oldBottom,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom
|
||||
this,
|
||||
oldLeft,
|
||||
oldTop,
|
||||
oldRight,
|
||||
oldBottom,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom
|
||||
)
|
||||
}
|
||||
|
||||
private fun createWindowInsets(
|
||||
topCutout: Rect? = Rect()
|
||||
): WindowInsets {
|
||||
private fun createWindowInsets(topCutout: Rect? = Rect()): WindowInsets {
|
||||
val windowInsets: WindowInsets = mock()
|
||||
val displayCutout: DisplayCutout = mock()
|
||||
whenever(windowInsets.displayCutout)
|
||||
@@ -795,17 +891,30 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
private fun setUpDefaultInsets() {
|
||||
whenever(combinedShadeHeadersConstraintManager
|
||||
.edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
|
||||
).thenReturn(EMPTY_CHANGES)
|
||||
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(
|
||||
combinedShadeHeadersConstraintManager.centerCutoutConstraints(
|
||||
Mockito.anyBoolean(),
|
||||
anyInt()
|
||||
)
|
||||
)
|
||||
.thenReturn(EMPTY_CHANGES)
|
||||
whenever(
|
||||
combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(
|
||||
Mockito.anyBoolean()
|
||||
)
|
||||
)
|
||||
.thenReturn(EMPTY_CHANGES)
|
||||
whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
|
||||
.thenReturn(Pair(0, 0).toAndroidPair())
|
||||
whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(false)
|
||||
@@ -814,11 +923,11 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
|
||||
private fun setupCurrentInsets(cutout: DisplayCutout?) {
|
||||
val mockedDisplay =
|
||||
mock<Display>().also { display -> whenever(display.cutout).thenReturn(cutout) }
|
||||
mock<Display>().also { display -> whenever(display.cutout).thenReturn(cutout) }
|
||||
whenever(viewContext.display).thenReturn(mockedDisplay)
|
||||
}
|
||||
|
||||
private fun<T, U> Pair<T, U>.toAndroidPair(): android.util.Pair<T, U> {
|
||||
private fun <T, U> Pair<T, U>.toAndroidPair(): android.util.Pair<T, U> {
|
||||
return android.util.Pair(first, second)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user