Merge "Implement new QQS/QS headers" into sc-dev

This commit is contained in:
Fabian Kozynski
2021-03-30 13:12:05 +00:00
committed by Android (Google) Code Review
18 changed files with 331 additions and 641 deletions

View File

@@ -30,9 +30,7 @@
<!-- Height of the status bar -->
<dimen name="status_bar_height">@dimen/status_bar_height_landscape</dimen>
<!-- Height of area above QQS where battery/time go -->
<dimen name="quick_qs_offset_height">@dimen/status_bar_height_landscape</dimen>
<!-- Total height of QQS in landscape, this is effectively status_bar_height_landscape + 128 -->
<dimen name="quick_qs_total_height">152dp</dimen>
<dimen name="quick_qs_offset_height">48dp</dimen>
<!-- Default height of an action bar. -->
<dimen name="action_bar_default_height">40dip</dimen>
<!-- Vertical padding around action bar icons. -->

View File

@@ -47,10 +47,6 @@
<dimen name="status_bar_height_landscape">@dimen/status_bar_height_portrait</dimen>
<!-- Height of area above QQS where battery/time go -->
<dimen name="quick_qs_offset_height">48dp</dimen>
<!-- Total height of QQS (quick_qs_offset_height + 128) -->
<dimen name="quick_qs_total_height">176dp</dimen>
<!-- Total height of QQS with two rows to fit media player (quick_qs_offset_height + 176) -->
<dimen name="quick_qs_total_height_with_media">224dp</dimen>
<!-- Height of the bottom navigation / system bar. -->
<dimen name="navigation_bar_height">48dp</dimen>
<!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->

View File

@@ -1716,8 +1716,6 @@
<java-symbol type="dimen" name="status_bar_height" />
<java-symbol type="dimen" name="display_cutout_touchable_region_size" />
<java-symbol type="dimen" name="quick_qs_offset_height" />
<java-symbol type="dimen" name="quick_qs_total_height" />
<java-symbol type="dimen" name="quick_qs_total_height_with_media" />
<java-symbol type="drawable" name="ic_jog_dial_sound_off" />
<java-symbol type="drawable" name="ic_jog_dial_sound_on" />
<java-symbol type="drawable" name="ic_jog_dial_unlock" />

View File

@@ -18,32 +18,52 @@
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/quick_qs_status_icons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/qs_header_top_padding"
android:paddingBottom="@dimen/qs_header_bottom_padding"
android:layout_below="@id/quick_status_bar_system_icons"
android:layout_height="@*android:dimen/quick_qs_offset_height"
android:clipChildren="false"
android:clipToPadding="false"
android:minHeight="20dp"
android:minHeight="48dp"
android:clickable="false"
android:focusable="true"
android:theme="@style/QSHeaderTheme">
<com.android.systemui.statusbar.policy.DateView
android:id="@+id/date"
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:gravity="center_vertical"
android:layout_height="match_parent"
android:minWidth="48dp"
android:minHeight="48dp"
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"
systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
android:textAppearance="@style/TextAppearance.StatusBar.Clock" />
<View
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="1"
/>
<!-- Will hold security footer in landscape with media -->
<FrameLayout
android:id="@+id/header_text_container"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:gravity="center"
/>
<include layout="@layout/qs_carrier_group"
android:id="@+id/carrier_group"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minHeight="48dp"
android:layout_gravity="end|center_vertical"
android:focusable="false"/>
<com.android.systemui.statusbar.phone.StatusIconContainer
android:id="@+id/statusIcons"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingEnd="@dimen/signal_cluster_battery_padding" />
<com.android.systemui.BatteryMeterView

View File

@@ -1,101 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2018 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"
android:id="@+id/header_text_container"
android:layout_width="match_parent"
android:layout_height="@dimen/qs_header_tooltip_height"
android:layout_below="@id/quick_status_bar_system_icons"
android:visibility="invisible"
android:theme="@style/QSHeaderTheme"
android:forceHasOverlappingRendering="false">
<com.android.systemui.qs.QSHeaderInfoLayout
android:id="@+id/status_container"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent">
<LinearLayout
android:id = "@+id/alarm_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:focusable="true"
android:clickable="true">
<ImageView
android:id="@+id/next_alarm_icon"
android:layout_width="@dimen/qs_header_alarm_icon_size"
android:layout_height="@dimen/qs_header_alarm_icon_size"
android:src="@drawable/ic_alarm"
android:contentDescription="@string/accessibility_quick_settings_alarm_set"
android:visibility="gone"/>
<com.android.systemui.util.AutoMarqueeTextView
android:id="@+id/next_alarm_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:layout_marginStart="@dimen/qs_header_alarm_text_margin_start"
android:textAppearance="@style/TextAppearance.QS.Status"
android:visibility="gone"/>
</LinearLayout>
<View
android:id="@+id/status_separator"
android:layout_width="@dimen/qs_header_separator_width"
android:layout_height="match_parent"
android:visibility="gone"/>
<LinearLayout
android:id = "@+id/ringer_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:focusable="true"
android:clickable="true">
<ImageView
android:id="@+id/ringer_mode_icon"
android:layout_width="@dimen/qs_header_alarm_icon_size"
android:layout_height="@dimen/qs_header_alarm_icon_size"
android:visibility="gone"/>
<com.android.systemui.util.AutoMarqueeTextView
android:id="@+id/ringer_mode_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:layout_marginStart="@dimen/qs_header_alarm_text_margin_start"
android:textAppearance="@style/TextAppearance.QS.Status"
android:visibility="gone"/>
</LinearLayout>
</com.android.systemui.qs.QSHeaderInfoLayout>
<include layout="@layout/qs_carrier_group"
android:id="@+id/carrier_group"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/qs_status_separator"
android:layout_gravity="end|center_vertical"
android:focusable="false"/>
</LinearLayout>

View File

@@ -32,26 +32,32 @@
android:paddingStart="0dp"
android:elevation="4dp" >
<!-- The clock -->
<include layout="@layout/quick_status_bar_header_system_icons" />
<!-- Date and privacy. Only visible in QS -->
<include layout="@layout/quick_status_bar_header_date_privacy"/>
<!-- Status icons within the panel itself (and not in the top-most status bar) -->
<include layout="@layout/quick_qs_status_icons" />
<!-- Layout containing tooltips, alarm text, etc. -->
<include layout="@layout/quick_settings_header_info" />
<RelativeLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:clipChildren="false"
android:clipToPadding="false">
<!-- Time, icons and Carrier (only in QS) -->
<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:accessibilityTraversalAfter="@+id/date_time_group"
android:layout_marginTop="8dp"
android:accessibilityTraversalAfter="@id/quick_qs_status_icons"
android:accessibilityTraversalBefore="@id/expand_indicator"
android:clipChildren="false"
android:clipToPadding="false"
android:focusable="true"
android:paddingBottom="10dp"
android:importantForAccessibility="yes" />
</RelativeLayout>
</com.android.systemui.qs.QuickStatusBarHeader>

View File

@@ -17,33 +17,35 @@
<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_system_icons"
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:clickable="true"
android:paddingTop="@dimen/status_bar_padding_top" >
android:paddingTop="@dimen/status_bar_padding_top"
android:minHeight="48dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:minHeight="48dp"
android:layout_weight="1"
android:orientation="horizontal"
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:minWidth="48dp"
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.StatusBar.Clock" />
<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" />
</LinearLayout>
<android.widget.Space
@@ -56,6 +58,7 @@
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:minHeight="48dp"
android:layout_weight="1"
android:orientation="horizontal"
android:gravity="center_vertical|end" >

View File

@@ -65,12 +65,14 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
private final QuickQSPanel mQuickQsPanel;
private final QSPanelController mQsPanelController;
private final QuickQSPanelController mQuickQSPanelController;
private final QuickStatusBarHeader mQuickStatusBarHeader;
private final QSSecurityFooter mSecurityFooter;
private final QS mQs;
private PagedTileLayout mPagedLayout;
private boolean mOnFirstPage = true;
private QSExpansionPathInterpolator mQSExpansionPathInterpolator;
private TouchAnimator mFirstPageAnimator;
private TouchAnimator mFirstPageDelayedAnimator;
private TouchAnimator mTranslationXAnimator;
@@ -98,19 +100,22 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
private final FeatureFlags mFeatureFlags;
@Inject
public QSAnimator(QS qs, QuickQSPanel quickPanel, QSPanelController qsPanelController,
public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
QSPanelController qsPanelController,
QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService,
FeatureFlags featureFlags) {
FeatureFlags featureFlags, QSExpansionPathInterpolator qsExpansionPathInterpolator) {
mQs = qs;
mQuickQsPanel = quickPanel;
mQsPanelController = qsPanelController;
mQuickQSPanelController = quickQSPanelController;
mQuickStatusBarHeader = quickStatusBarHeader;
mSecurityFooter = securityFooter;
mHost = qsTileHost;
mExecutor = executor;
mTunerService = tunerService;
mFeatureFlags = featureFlags;
mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
mHost.addCallback(this);
mQsPanelController.addOnAttachStateChangeListener(this);
qs.getView().addOnLayoutChangeListener(this);
@@ -252,7 +257,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
if (count < tileLayout.getNumVisibleTiles()) {
getRelativePosition(loc1, quickTileView, view);
getRelativePosition(loc2, tileView, view);
int yOffset = qsSideLabelsEnabled ? loc2[1] - loc1[1] : 0;
int yOffset = qsSideLabelsEnabled
? loc2[1] - loc1[1]
: mQuickStatusBarHeader.getOffsetTranslation();
// Move the quick tile right from its location to the new one.
View v = qsSideLabelsEnabled ? quickTileView.getIcon() : quickTileView;
translationXBuilder.addFloat(v, "translationX", 0, xDiff);
@@ -266,8 +273,14 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
translationYBuilder.addFloat(v, "translationY", -yDiff + yOffset, 0);
if (qsSideLabelsEnabled) {
translationYBuilder.addFloat(quickTileView, "translationY", 0, yOffset);
translationYBuilder.addFloat(tileView, "translationY", -yOffset, 0);
// Offset the translation animation on the views
// (that goes from 0 to getOffsetTranslation)
int offsetWithQSBHTranslation =
yOffset - mQuickStatusBarHeader.getOffsetTranslation();
translationYBuilder.addFloat(quickTileView, "translationY", 0,
offsetWithQSBHTranslation);
translationYBuilder.addFloat(tileView, "translationY",
-offsetWithQSBHTranslation, 0);
if (mQQSTileHeightAnimator == null) {
mQQSTileHeightAnimator = new HeightExpansionAnimator(this,
@@ -375,22 +388,23 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
}
float px = 0;
float py = 1;
if (tiles.size() <= 3) {
px = 1;
} else if (tiles.size() <= 6) {
px = .4f;
}
PathInterpolatorBuilder interpolatorBuilder = new PathInterpolatorBuilder(0, 0, px, py);
translationXBuilder.setInterpolator(interpolatorBuilder.getXInterpolator());
translationYBuilder.setInterpolator(interpolatorBuilder.getYInterpolator());
mQSExpansionPathInterpolator.setControlX2(px);
translationXBuilder.setInterpolator(mQSExpansionPathInterpolator.getXInterpolator());
translationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator());
mTranslationXAnimator = translationXBuilder.build();
mTranslationYAnimator = translationYBuilder.build();
if (mQQSTileHeightAnimator != null) {
mQQSTileHeightAnimator.setInterpolator(interpolatorBuilder.getYInterpolator());
mQQSTileHeightAnimator.setInterpolator(
mQSExpansionPathInterpolator.getYInterpolator());
}
if (mOtherTilesExpandAnimator != null) {
mOtherTilesExpandAnimator.setInterpolator(interpolatorBuilder.getYInterpolator());
mOtherTilesExpandAnimator.setInterpolator(
mQSExpansionPathInterpolator.getYInterpolator());
}
}
mNonfirstPageAnimator = new TouchAnimator.Builder()

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.qs
import android.view.animation.Interpolator
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class QSExpansionPathInterpolator @Inject constructor() {
private var pathInterpolatorBuilder = PathInterpolatorBuilder(0f, 0f, 0f, 1f)
private var lastX = 0f
val xInterpolator: Interpolator
get() = pathInterpolatorBuilder.xInterpolator
val yInterpolator: Interpolator
get() = pathInterpolatorBuilder.yInterpolator
fun setControlX2(value: Float) {
if (value != lastX) {
lastX = value
pathInterpolatorBuilder = PathInterpolatorBuilder(0f, 0f, lastX, 1f)
}
}
}

View File

@@ -1,131 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.qs
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import com.android.systemui.R
/**
* Container for the Next Alarm and Ringer status texts in [QuickStatusBarHeader].
*
* If both elements are visible, it splits the available space according to the following rules:
* * If both views add up to less than the total space, they take all the space they need.
* * If both views are larger than half the space, each view takes half the space.
* * Otherwise, the smaller view takes the space it needs and the larger one takes all remaining
* space.
*/
class QSHeaderInfoLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyle, defStyleRes) {
private lateinit var alarmContainer: View
private lateinit var ringerContainer: View
private lateinit var statusSeparator: View
private val location = Location(0, 0)
override fun onFinishInflate() {
super.onFinishInflate()
alarmContainer = findViewById(R.id.alarm_container)
ringerContainer = findViewById(R.id.ringer_container)
statusSeparator = findViewById(R.id.status_separator)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
// At most one view is there
if (statusSeparator.visibility == View.GONE) super.onLayout(changed, l, t, r, b)
else {
val layoutRTL = isLayoutRtl
val width = r - l
val height = b - t
var offset = 0
offset += alarmContainer.layoutView(width, height, offset, layoutRTL)
offset += statusSeparator.layoutView(width, height, offset, layoutRTL)
ringerContainer.layoutView(width, height, offset, layoutRTL)
}
}
private fun View.layoutView(pWidth: Int, pHeight: Int, offset: Int, RTL: Boolean): Int {
location.setLocationFromOffset(pWidth, offset, this.measuredWidth, RTL)
layout(location.left, 0, location.right, pHeight)
return this.measuredWidth
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(
MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST),
heightMeasureSpec)
val width = MeasureSpec.getSize(widthMeasureSpec)
// Once we measure the views, using as much space as they need, we need to remeasure them
// assigning them their final width. This is because TextViews decide whether to MARQUEE
// after onMeasure.
if (statusSeparator.visibility != View.GONE) {
val alarmWidth = alarmContainer.measuredWidth
val separatorWidth = statusSeparator.measuredWidth
val ringerWidth = ringerContainer.measuredWidth
val availableSpace = MeasureSpec.getSize(width) - separatorWidth
if (alarmWidth < availableSpace / 2) {
measureChild(
ringerContainer,
MeasureSpec.makeMeasureSpec(
Math.min(ringerWidth, availableSpace - alarmWidth),
MeasureSpec.AT_MOST),
heightMeasureSpec)
} else if (ringerWidth < availableSpace / 2) {
measureChild(alarmContainer,
MeasureSpec.makeMeasureSpec(
Math.min(alarmWidth, availableSpace - ringerWidth),
MeasureSpec.AT_MOST),
heightMeasureSpec)
} else {
measureChild(
alarmContainer,
MeasureSpec.makeMeasureSpec(availableSpace / 2, MeasureSpec.AT_MOST),
heightMeasureSpec)
measureChild(
ringerContainer,
MeasureSpec.makeMeasureSpec(availableSpace / 2, MeasureSpec.AT_MOST),
heightMeasureSpec)
}
}
setMeasuredDimension(width, measuredHeight)
}
private data class Location(var left: Int, var right: Int) {
/**
* Sets the [left] and [right] with the correct values for laying out the child, respecting
* RTL. Only set the variable through here to prevent concurrency issues.
* This is done to prevent allocation of [Pair] in [onLayout].
*/
fun setLocationFromOffset(parentWidth: Int, offset: Int, width: Int, RTL: Boolean) {
if (RTL) {
left = parentWidth - offset - width
right = parentWidth - offset
} else {
left = offset
right = offset + width
}
}
}
}

View File

@@ -391,20 +391,13 @@ public class QSPanel extends LinearLayout implements Tunable {
index++;
if (mSecurityFooter != null) {
LinearLayout.LayoutParams layoutParams =
(LayoutParams) mSecurityFooter.getLayoutParams();
if (mUsingHorizontalLayout && mHeaderContainer != null) {
// Adding the security view to the header, that enables us to avoid scrolling
layoutParams.width = 0;
layoutParams.weight = 1.6f;
switchToParent(mSecurityFooter, mHeaderContainer, 1 /* always in second place */);
switchToParent(mSecurityFooter, mHeaderContainer, 0);
} else {
layoutParams.width = LayoutParams.WRAP_CONTENT;
layoutParams.weight = 0;
switchToParent(mSecurityFooter, parent, index);
index++;
}
mSecurityFooter.setLayoutParams(layoutParams);
}
if (mFooter != null) {

View File

@@ -17,15 +17,14 @@ package com.android.systemui.qs;
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.ColorInt;
import android.app.AlarmManager.AlarmClockInfo;
import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
import android.media.AudioManager;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.util.Pair;
@@ -34,66 +33,46 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Space;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import com.android.settingslib.Utils;
import com.android.systemui.BatteryMeterView;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.qs.QSDetail.Callback;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
import java.util.Locale;
import java.util.Objects;
/**
* View that contains the top-most bits of the screen (primarily the status bar with date, time, and
* battery) and also contains the {@link QuickQSPanel} along with some of the panel's inner
* contents.
* 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}.
*/
public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwner {
public static final int MAX_TOOLTIP_SHOWN_COUNT = 2;
public class QuickStatusBarHeader extends FrameLayout {
private boolean mExpanded;
private boolean mQsDisabled;
private TouchAnimator mAlphaAnimator;
private TouchAnimator mTranslationAnimator;
protected QuickQSPanel mHeaderQsPanel;
private TouchAnimator mStatusIconsAlphaAnimator;
private TouchAnimator mHeaderTextContainerAlphaAnimator;
private View mDatePrivacyView;
private View mClockIconsView;
private View mContainer;
private View mSystemIconsView;
private View mQuickQsStatusIcons;
private View mHeaderTextContainerView;
private ImageView mNextAlarmIcon;
/** {@link TextView} containing the actual text indicating when the next alarm will go off. */
private TextView mNextAlarmTextView;
private View mNextAlarmContainer;
private View mStatusSeparator;
private ImageView mRingerModeIcon;
private TextView mRingerModeTextView;
private View mRingerContainer;
private View mQSCarriers;
private Clock mClockView;
private OngoingPrivacyChip mPrivacyChip;
private Space mSpace;
private BatteryMeterView mBatteryRemainingIcon;
private TintedIconManager mTintedIconManager;
private StatusIconContainer mIconContainer;
// Used for RingerModeTracker
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
private TintedIconManager mTintedIconManager;
private QSExpansionPathInterpolator mQSExpansionPathInterpolator;
private int mStatusBarPaddingTop = 0;
private int mRoundedCornerPadding = 0;
@@ -102,12 +81,26 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn
private int mWaterfallTopInset;
private int mCutOutPaddingLeft;
private int mCutOutPaddingRight;
private float mExpandedHeaderAlpha = 1.0f;
private float mClockIconsAlpha = 1.0f;
private float mDatePrivacyAlpha = 1.0f;
private float mKeyguardExpansionFraction;
private int mTextColorPrimary = Color.TRANSPARENT;
private int mTopViewMeasureHeight;
private final String mMobileSlotName;
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
mMobileSlotName = context.getString(com.android.internal.R.string.status_bar_mobile);
}
/**
* 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
@@ -115,19 +108,12 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn
super.onFinishInflate();
mHeaderQsPanel = findViewById(R.id.quick_qs_panel);
mSystemIconsView = findViewById(R.id.quick_status_bar_system_icons);
mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons);
mDatePrivacyView = findViewById(R.id.quick_status_bar_date_privacy);
mClockIconsView = findViewById(R.id.quick_qs_status_icons);
mQSCarriers = findViewById(R.id.carrier_group);
mContainer = findViewById(R.id.container);
mIconContainer = findViewById(R.id.statusIcons);
// Views corresponding to the header info section (e.g. ringer and next alarm).
mHeaderTextContainerView = findViewById(R.id.header_text_container);
mStatusSeparator = findViewById(R.id.status_separator);
mNextAlarmIcon = findViewById(R.id.next_alarm_icon);
mNextAlarmTextView = findViewById(R.id.next_alarm_text);
mNextAlarmContainer = findViewById(R.id.alarm_container);
mRingerModeIcon = findViewById(R.id.ringer_mode_icon);
mRingerModeTextView = findViewById(R.id.ringer_mode_text);
mRingerContainer = findViewById(R.id.ringer_container);
mPrivacyChip = findViewById(R.id.privacy_chip);
mClockView = findViewById(R.id.clock);
mSpace = findViewById(R.id.space);
// Tint for the battery icons are handled in setupHost()
@@ -140,79 +126,34 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn
// QS will always show the estimate, and BatteryMeterView handles the case where
// it's unavailable or charging
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
mRingerModeTextView.setSelected(true);
mNextAlarmTextView.setSelected(true);
}
void onAttach(TintedIconManager iconManager) {
void onAttach(TintedIconManager iconManager,
QSExpansionPathInterpolator qsExpansionPathInterpolator) {
mTintedIconManager = iconManager;
int fillColor = Utils.getColorAttrDefaultColor(getContext(),
android.R.attr.textColorPrimary);
// Set the correct tint for the status icons so they contrast
iconManager.setTint(fillColor);
mNextAlarmIcon.setImageTintList(ColorStateList.valueOf(fillColor));
mRingerModeIcon.setImageTintList(ColorStateList.valueOf(fillColor));
mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
updateAnimators();
}
public QuickQSPanel getHeaderQsPanel() {
return mHeaderQsPanel;
}
void updateStatusText(int ringerMode, AlarmClockInfo nextAlarm, boolean zenOverridingRinger,
boolean use24HourFormat) {
boolean changed = updateRingerStatus(ringerMode, zenOverridingRinger)
|| updateAlarmStatus(nextAlarm, use24HourFormat);
if (changed) {
boolean alarmVisible = mNextAlarmTextView.getVisibility() == View.VISIBLE;
boolean ringerVisible = mRingerModeTextView.getVisibility() == View.VISIBLE;
mStatusSeparator.setVisibility(alarmVisible && ringerVisible ? View.VISIBLE
: View.GONE);
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mDatePrivacyView.getMeasuredHeight() != mTopViewMeasureHeight) {
mTopViewMeasureHeight = mDatePrivacyView.getMeasuredHeight();
updateAnimators();
}
}
private boolean updateRingerStatus(int ringerMode, boolean zenOverridingRinger) {
boolean isOriginalVisible = mRingerModeTextView.getVisibility() == View.VISIBLE;
CharSequence originalRingerText = mRingerModeTextView.getText();
boolean ringerVisible = false;
if (!zenOverridingRinger) {
if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
mRingerModeIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate);
mRingerModeTextView.setText(R.string.qs_status_phone_vibrate);
ringerVisible = true;
} else if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
mRingerModeIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
mRingerModeTextView.setText(R.string.qs_status_phone_muted);
ringerVisible = true;
}
}
mRingerModeIcon.setVisibility(ringerVisible ? View.VISIBLE : View.GONE);
mRingerModeTextView.setVisibility(ringerVisible ? View.VISIBLE : View.GONE);
mRingerContainer.setVisibility(ringerVisible ? View.VISIBLE : View.GONE);
return isOriginalVisible != ringerVisible ||
!Objects.equals(originalRingerText, mRingerModeTextView.getText());
}
private boolean updateAlarmStatus(AlarmClockInfo nextAlarm, boolean use24HourFormat) {
boolean isOriginalVisible = mNextAlarmTextView.getVisibility() == View.VISIBLE;
CharSequence originalAlarmText = mNextAlarmTextView.getText();
boolean alarmVisible = false;
if (nextAlarm != null) {
alarmVisible = true;
mNextAlarmTextView.setText(formatNextAlarm(nextAlarm, use24HourFormat));
}
mNextAlarmIcon.setVisibility(alarmVisible ? View.VISIBLE : View.GONE);
mNextAlarmTextView.setVisibility(alarmVisible ? View.VISIBLE : View.GONE);
mNextAlarmContainer.setVisibility(alarmVisible ? View.VISIBLE : View.GONE);
return isOriginalVisible != alarmVisible ||
!Objects.equals(originalAlarmText, mNextAlarmTextView.getText());
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -225,40 +166,27 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn
updateResources();
}
/**
* The height of QQS should always be the status bar height + 128dp. This is normally easy, but
* when there is a notch involved the status bar can remain a fixed pixel size.
*/
private void updateMinimumHeight() {
int sbHeight = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height);
int qqsHeight = mContext.getResources().getDimensionPixelSize(
R.dimen.qs_quick_header_panel_height);
setMinimumHeight(sbHeight + qqsHeight);
}
void updateResources() {
Resources resources = mContext.getResources();
updateMinimumHeight();
mRoundedCornerPadding = resources.getDimensionPixelSize(
R.dimen.rounded_corner_content_padding);
mStatusBarPaddingTop = resources.getDimensionPixelSize(R.dimen.status_bar_padding_top);
// Update height for a few views, especially due to landscape mode restricting space.
mHeaderTextContainerView.getLayoutParams().height =
resources.getDimensionPixelSize(R.dimen.qs_header_tooltip_height);
mHeaderTextContainerView.setLayoutParams(mHeaderTextContainerView.getLayoutParams());
mSystemIconsView.getLayoutParams().height = resources.getDimensionPixelSize(
int qsOffsetHeight = resources.getDimensionPixelSize(
com.android.internal.R.dimen.quick_qs_offset_height);
mSystemIconsView.setLayoutParams(mSystemIconsView.getLayoutParams());
mDatePrivacyView.getLayoutParams().height =
Math.max(qsOffsetHeight, mDatePrivacyView.getMinimumHeight());
mDatePrivacyView.setLayoutParams(mDatePrivacyView.getLayoutParams());
mClockIconsView.getLayoutParams().height =
Math.max(qsOffsetHeight, mClockIconsView.getMinimumHeight());
mClockIconsView.setLayoutParams(mClockIconsView.getLayoutParams());
ViewGroup.LayoutParams lp = getLayoutParams();
if (mQsDisabled) {
lp.height = resources.getDimensionPixelSize(
com.android.internal.R.dimen.quick_qs_offset_height);
lp.height = mClockIconsView.getLayoutParams().height;
} else {
lp.height = WRAP_CONTENT;
}
@@ -276,21 +204,45 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn
mBatteryRemainingIcon.updateColors(mTextColorPrimary, textColorSecondary,
mTextColorPrimary);
}
updateStatusIconAlphaAnimator();
updateHeaderTextContainerAlphaAnimator();
updateHeadersPadding();
updateAnimators();
}
private void updateStatusIconAlphaAnimator() {
mStatusIconsAlphaAnimator = new TouchAnimator.Builder()
.addFloat(mQuickQsStatusIcons, "alpha", 1, 0, 0)
private void updateAnimators() {
updateAlphaAnimator();
int offset = mTopViewMeasureHeight;
mTranslationAnimator = new TouchAnimator.Builder()
.addFloat(mContainer, "translationY", 0, offset)
.setInterpolator(mQSExpansionPathInterpolator != null
? mQSExpansionPathInterpolator.getYInterpolator()
: null)
.build();
}
private void updateHeaderTextContainerAlphaAnimator() {
mHeaderTextContainerAlphaAnimator = new TouchAnimator.Builder()
.addFloat(mHeaderTextContainerView, "alpha", 0, 0, mExpandedHeaderAlpha)
.build();
private void updateAlphaAnimator() {
StatusBarMobileView icon =
((StatusBarMobileView) mIconContainer.getViewForSlot(mMobileSlotName));
TouchAnimator.Builder builder = new TouchAnimator.Builder()
.addFloat(mQSCarriers, "alpha", 0, 1)
.addFloat(mDatePrivacyView, "alpha", 0, mDatePrivacyAlpha);
if (icon != null) {
builder.addFloat(icon, "alpha", 1, 0);
builder.setListener(new TouchAnimator.ListenerAdapter() {
@Override
public void onAnimationAtEnd() {
icon.forceHidden(true);
icon.setVisibleState(STATE_HIDDEN);
}
@Override
public void onAnimationStarted() {
icon.forceHidden(false);
icon.setVisibleState(STATE_ICON);
}
});
}
mAlphaAnimator = builder.build();
}
/** */
@@ -312,25 +264,19 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn
public void setExpansion(boolean forceExpanded, float expansionFraction,
float panelTranslationY) {
final float keyguardExpansionFraction = forceExpanded ? 1f : expansionFraction;
if (mStatusIconsAlphaAnimator != null) {
mStatusIconsAlphaAnimator.setPosition(keyguardExpansionFraction);
}
if (mAlphaAnimator != null) {
mAlphaAnimator.setPosition(keyguardExpansionFraction);
}
if (mTranslationAnimator != null) {
mTranslationAnimator.setPosition(keyguardExpansionFraction);
}
// If forceExpanded (we are opening QS from lockscreen), the animators have been set to
// position = 1f.
if (forceExpanded) {
// If the keyguard is showing, we want to offset the text so that it comes in at the
// same time as the panel as it slides down.
mHeaderTextContainerView.setTranslationY(panelTranslationY);
setTranslationY(panelTranslationY);
} else {
mHeaderTextContainerView.setTranslationY(0f);
}
if (mHeaderTextContainerAlphaAnimator != null) {
mHeaderTextContainerAlphaAnimator.setPosition(keyguardExpansionFraction);
if (keyguardExpansionFraction > 0) {
mHeaderTextContainerView.setVisibility(VISIBLE);
} else {
mHeaderTextContainerView.setVisibility(INVISIBLE);
}
setTranslationY(0);
}
mKeyguardExpansionFraction = keyguardExpansionFraction;
@@ -341,28 +287,21 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn
if (disabled == mQsDisabled) return;
mQsDisabled = disabled;
mHeaderQsPanel.setDisabledByPolicy(disabled);
mHeaderTextContainerView.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
mQuickQsStatusIcons.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
mClockIconsView.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
updateResources();
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
// Handle padding of the clock
// Handle padding of the views
DisplayCutout cutout = insets.getDisplayCutout();
Pair<Integer, Integer> cornerCutoutPadding = StatusBarWindowView.cornerCutoutMargins(
cutout, getDisplay());
Pair<Integer, Integer> padding =
StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
cutout, cornerCutoutPadding, -1);
if (padding == null) {
mSystemIconsView.setPaddingRelative(
getResources().getDimensionPixelSize(R.dimen.status_bar_padding_start), 0,
getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end), 0);
} else {
mSystemIconsView.setPadding(padding.first, 0, padding.second, 0);
}
mDatePrivacyView.setPadding(padding.first, 0, padding.second, 0);
mClockIconsView.setPadding(padding.first, 0, padding.second, 0);
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mSpace.getLayoutParams();
boolean cornerCutout = cornerCutoutPadding != null
&& (cornerCutoutPadding.first == 0 || cornerCutoutPadding.second == 0);
@@ -380,13 +319,13 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn
mCutOutPaddingLeft = padding.first;
mCutOutPaddingRight = padding.second;
mWaterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top;
updateClockPadding();
updateHeadersPadding();
return super.onApplyWindowInsets(insets);
}
private void updateClockPadding() {
int clockPaddingLeft = 0;
int clockPaddingRight = 0;
private void updateHeadersPadding() {
int paddingLeft = 0;
int paddingRight = 0;
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
int leftMargin = lp.leftMargin;
@@ -399,18 +338,22 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn
// if there's a cutout, let's use at least the rounded corner inset
int cutoutPadding = Math.max(mCutOutPaddingLeft, mRoundedCornerPadding);
int contentMarginLeft = isLayoutRtl() ? mContentMarginEnd : mContentMarginStart;
clockPaddingLeft = Math.max(cutoutPadding - contentMarginLeft - leftMargin, 0);
paddingLeft = Math.max(cutoutPadding - contentMarginLeft - 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);
int contentMarginRight = isLayoutRtl() ? mContentMarginStart : mContentMarginEnd;
clockPaddingRight = Math.max(cutoutPadding - contentMarginRight - rightMargin, 0);
paddingRight = Math.max(cutoutPadding - contentMarginRight - rightMargin, 0);
}
mSystemIconsView.setPadding(clockPaddingLeft,
mDatePrivacyView.setPadding(paddingLeft,
mWaterfallTopInset + mStatusBarPaddingTop,
clockPaddingRight,
paddingRight,
0);
mClockIconsView.setPadding(paddingLeft,
mWaterfallTopInset + mStatusBarPaddingTop,
paddingRight,
0);
}
@@ -422,26 +365,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn
mHeaderQsPanel.setCallback(qsPanelCallback);
}
private String formatNextAlarm(AlarmClockInfo info, boolean use24HourFormat) {
if (info == null) {
return "";
}
String skeleton = use24HourFormat ? "EHm" : "Ehma";
String pattern = android.text.format.DateFormat
.getBestDateTimePattern(Locale.getDefault(), skeleton);
return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString();
}
public static float getColorIntensity(@ColorInt int color) {
return color == Color.WHITE ? 0 : 1;
}
@NonNull
@Override
public Lifecycle getLifecycle() {
return mLifecycle;
}
/** */
public void setContentMargins(int marginStart, int marginEnd,
QuickQSPanelController quickQSPanelController) {
@@ -459,24 +382,39 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn
view.setLayoutParams(lp);
}
}
updateClockPadding();
updateHeadersPadding();
}
/**
* When QS is scrolling, mClockIconsAlpha should scroll away and fade out.
*
* For a given scroll level, this method does the following:
* <ol>
* <li>Determine the alpha that {@code mClockIconsView} should have when the panel is fully
* expanded.</li>
* <li>Set the scroll of {@code mClockIconsView} to the same of {@code QSPanel}.</li>
* <li>Set the alpha of {@code mClockIconsView} to that determined by the expansion of
* the panel, interpolated between 1 (no expansion) and {@code mClockIconsAlpha} (fully
* expanded), matching the animator.</li>
* </ol>
*
* @param scrollY the scroll of the QSPanel container
*/
public void setExpandedScrollAmount(int scrollY) {
// The scrolling of the expanded qs has changed. Since the header text isn't part of it,
// but would overlap content, we're fading it out.
float newAlpha = 1.0f;
if (mHeaderTextContainerView.getHeight() > 0) {
newAlpha = MathUtils.map(0, mHeaderTextContainerView.getHeight() / 2.0f, 1.0f, 0.0f,
if (mClockIconsView.getHeight() > 0) {
newAlpha = MathUtils.map(0, mClockIconsView.getHeight() / 2.0f, 1.0f, 0.0f,
scrollY);
newAlpha = Interpolators.ALPHA_OUT.getInterpolation(newAlpha);
}
mHeaderTextContainerView.setScrollY(scrollY);
if (newAlpha != mExpandedHeaderAlpha) {
mExpandedHeaderAlpha = newAlpha;
mHeaderTextContainerView.setAlpha(MathUtils.lerp(0.0f, mExpandedHeaderAlpha,
mClockIconsView.setScrollY(scrollY);
if (newAlpha != mClockIconsAlpha) {
mClockIconsAlpha = newAlpha;
mClockIconsView.setAlpha(MathUtils.lerp(1.0f, mClockIconsAlpha,
mKeyguardExpansionFraction));
updateHeaderTextContainerAlphaAnimator();
updateAlphaAnimator();
}
}
}

View File

@@ -16,21 +16,13 @@
package com.android.systemui.qs;
import android.app.AlarmManager.AlarmClockInfo;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Bundle;
import android.provider.AlarmClock;
import android.provider.Settings;
import android.service.notification.ZenModeConfig;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.logging.UiEventLogger;
@@ -47,15 +39,9 @@ import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.privacy.logging.PrivacyLogger;
import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.settings.UserTracker;
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.NextAlarmController;
import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.ZenModeController.Callback;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.ViewController;
import java.util.ArrayList;
@@ -70,75 +56,29 @@ import javax.inject.Inject;
class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader> {
private static final String TAG = "QuickStatusBarHeader";
private final ZenModeController mZenModeController;
private final NextAlarmController mNextAlarmController;
private final PrivacyItemController mPrivacyItemController;
private final RingerModeTracker mRingerModeTracker;
private final ActivityStarter mActivityStarter;
private final UiEventLogger mUiEventLogger;
private final QSCarrierGroupController mQSCarrierGroupController;
private final QuickQSPanelController mHeaderQsPanelController;
private final LifecycleRegistry mLifecycle;
private final OngoingPrivacyChip mPrivacyChip;
private final Clock mClockView;
private final View mNextAlarmContainer;
private final View mRingerContainer;
private final QSTileHost mQSTileHost;
private final StatusBarIconController mStatusBarIconController;
private final DemoModeController mDemoModeController;
private final UserTracker mUserTracker;
private final StatusIconContainer mIconContainer;
private final StatusBarIconController.TintedIconManager mIconManager;
private final DemoMode mDemoModeReceiver;
private final PrivacyLogger mPrivacyLogger;
private final PrivacyDialogController mPrivacyDialogController;
private final QSExpansionPathInterpolator mQSExpansionPathInterpolator;
private boolean mListening;
private AlarmClockInfo mNextAlarm;
private boolean mMicCameraIndicatorsEnabled;
private boolean mLocationIndicatorsEnabled;
private boolean mPrivacyChipLogged;
private SysuiColorExtractor mColorExtractor;
private ColorExtractor.OnColorsChangedListener mOnColorsChangedListener;
private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
private final ZenModeController.Callback mZenModeControllerCallback = new Callback() {
@Override
public void onZenChanged(int zen) {
mView.updateStatusText(mRingerMode, mNextAlarm, isZenOverridingRinger(),
use24HourFormat());
}
@Override
public void onConfigChanged(ZenModeConfig config) {
mView.updateStatusText(mRingerMode, mNextAlarm, isZenOverridingRinger(),
use24HourFormat());
}
};
private boolean use24HourFormat() {
return android.text.format.DateFormat.is24HourFormat(
mView.getContext(), mUserTracker.getUserId());
}
private final NextAlarmChangeCallback mNextAlarmChangeCallback = new NextAlarmChangeCallback() {
@Override
public void onNextAlarmChanged(AlarmClockInfo nextAlarm) {
mNextAlarm = nextAlarm;
mView.updateStatusText(mRingerMode, mNextAlarm, isZenOverridingRinger(),
use24HourFormat());
}
};
private final LifecycleOwner mLifecycleOwner = new LifecycleOwner() {
@NonNull
@Override
public Lifecycle getLifecycle() {
return mLifecycle;
}
};
private PrivacyItemController.Callback mPICCallback = new PrivacyItemController.Callback() {
@Override
@@ -176,63 +116,43 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
if (v == mClockView) {
mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
AlarmClock.ACTION_SHOW_ALARMS), 0);
} else if (v == mNextAlarmContainer && mNextAlarmContainer.isVisibleToUser()) {
if (mNextAlarm.getShowIntent() != null) {
mActivityStarter.postStartActivityDismissingKeyguard(
mNextAlarm.getShowIntent());
} else {
Log.d(TAG, "No PendingIntent for next alarm. Using default intent");
mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
AlarmClock.ACTION_SHOW_ALARMS), 0);
}
} else if (v == mPrivacyChip) {
// If the privacy chip is visible, it means there were some indicators
mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK);
mPrivacyDialogController.showDialog(getContext());
} else if (v == mRingerContainer && mRingerContainer.isVisibleToUser()) {
mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
Settings.ACTION_SOUND_SETTINGS), 0);
}
}
};
@Inject
QuickStatusBarHeaderController(QuickStatusBarHeader view,
ZenModeController zenModeController, NextAlarmController nextAlarmController,
PrivacyItemController privacyItemController, RingerModeTracker ringerModeTracker,
PrivacyItemController privacyItemController,
ActivityStarter activityStarter, UiEventLogger uiEventLogger,
QSTileHost qsTileHost, StatusBarIconController statusBarIconController,
StatusBarIconController statusBarIconController,
DemoModeController demoModeController,
UserTracker userTracker, QuickQSPanelController quickQSPanelController,
QuickQSPanelController quickQSPanelController,
QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder,
PrivacyLogger privacyLogger,
SysuiColorExtractor colorExtractor,
PrivacyDialogController privacyDialogController) {
PrivacyDialogController privacyDialogController,
QSExpansionPathInterpolator qsExpansionPathInterpolator) {
super(view);
mZenModeController = zenModeController;
mNextAlarmController = nextAlarmController;
mPrivacyItemController = privacyItemController;
mRingerModeTracker = ringerModeTracker;
mActivityStarter = activityStarter;
mUiEventLogger = uiEventLogger;
mQSTileHost = qsTileHost;
mStatusBarIconController = statusBarIconController;
mDemoModeController = demoModeController;
mUserTracker = userTracker;
mLifecycle = new LifecycleRegistry(mLifecycleOwner);
mHeaderQsPanelController = quickQSPanelController;
mPrivacyLogger = privacyLogger;
mPrivacyDialogController = privacyDialogController;
mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
mQSCarrierGroupController = qsCarrierGroupControllerBuilder
.setQSCarrierGroup(mView.findViewById(R.id.carrier_group))
.build();
mPrivacyChip = mView.findViewById(R.id.privacy_chip);
mNextAlarmContainer = mView.findViewById(R.id.alarm_container);
mClockView = mView.findViewById(R.id.clock);
mRingerContainer = mView.findViewById(R.id.ringer_container);
mIconContainer = mView.findViewById(R.id.statusIcons);
mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer);
@@ -247,15 +167,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
@Override
protected void onViewAttached() {
mRingerModeTracker.getRingerModeInternal().observe(mLifecycleOwner, ringer -> {
mRingerMode = ringer;
mView.updateStatusText(mRingerMode, mNextAlarm, isZenOverridingRinger(),
use24HourFormat());
});
mClockView.setOnClickListener(mOnClickListener);
mNextAlarmContainer.setOnClickListener(mOnClickListener);
mRingerContainer.setOnClickListener(mOnClickListener);
mPrivacyChip.setOnClickListener(mOnClickListener);
mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
@@ -268,18 +180,15 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE);
mView.onAttach(mIconManager);
mView.onAttach(mIconManager, mQSExpansionPathInterpolator);
mDemoModeController.addCallback(mDemoModeReceiver);
}
@Override
protected void onViewDetached() {
mRingerModeTracker.getRingerModeInternal().removeObservers(mLifecycleOwner);
mClockView.setOnClickListener(null);
mColorExtractor.removeOnColorsChangedListener(mOnColorsChangedListener);
mNextAlarmContainer.setOnClickListener(null);
mRingerContainer.setOnClickListener(null);
mPrivacyChip.setOnClickListener(null);
mStatusBarIconController.removeIconGroup(mIconManager);
mDemoModeController.removeCallback(mDemoModeReceiver);
@@ -304,17 +213,11 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
}
if (listening) {
mZenModeController.addCallback(mZenModeControllerCallback);
mNextAlarmController.addCallback(mNextAlarmChangeCallback);
mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
// Get the most up to date info
mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
mLocationIndicatorsEnabled = mPrivacyItemController.getLocationAvailable();
mPrivacyItemController.addCallback(mPICCallback);
} else {
mZenModeController.removeCallback(mZenModeControllerCallback);
mNextAlarmController.removeCallback(mNextAlarmChangeCallback);
mLifecycle.setCurrentState(Lifecycle.State.CREATED);
mPrivacyItemController.removeCallback(mPICCallback);
mPrivacyChipLogged = false;
}
@@ -357,11 +260,6 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
return mMicCameraIndicatorsEnabled || mLocationIndicatorsEnabled;
}
private boolean isZenOverridingRinger() {
return ZenModeConfig.isZenOverridingRinger(mZenModeController.getZen(),
mZenModeController.getConsolidatedPolicy());
}
public void setContentMargins(int contentPaddingStart, int contentPaddingEnd) {
mView.setContentMargins(contentPaddingStart, contentPaddingEnd, mHeaderQsPanelController);
}

View File

@@ -56,7 +56,6 @@ import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
import com.android.systemui.Prefs;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.DetailAdapter;
@@ -67,7 +66,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.PagedTileLayout.TilePage;
import com.android.systemui.qs.QSEvent;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QuickStatusBarHeader;
import com.android.systemui.qs.logging.QSLogger;
import java.io.FileDescriptor;
@@ -300,11 +298,6 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
getInstanceId());
mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state);
mHandler.sendEmptyMessage(H.LONG_CLICK);
Prefs.putInt(
mContext,
Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
QuickStatusBarHeader.MAX_TOOLTIP_SHOWN_COUNT);
}
public LogMaker populate(LogMaker logMaker) {

View File

@@ -59,6 +59,7 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
private View mMobileRoamingSpace;
private int mVisibleState = -1;
private DualToneHandler mDualToneHandler;
private boolean mForceHidden;
public static StatusBarMobileView fromContext(Context context, String slot) {
LayoutInflater inflater = LayoutInflater.from(context);
@@ -150,7 +151,7 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
private void initViewState() {
setContentDescription(mState.contentDescription);
if (!mState.visible) {
if (!mState.visible || mForceHidden) {
mMobileGroup.setVisibility(View.GONE);
} else {
mMobileGroup.setVisibility(View.VISIBLE);
@@ -176,8 +177,9 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
boolean needsLayout = false;
setContentDescription(state.contentDescription);
if (mState.visible != state.visible) {
mMobileGroup.setVisibility(state.visible ? View.VISIBLE : View.GONE);
int newVisibility = state.visible && !mForceHidden ? View.VISIBLE : View.GONE;
if (newVisibility != mMobileGroup.getVisibility()) {
mMobileGroup.setVisibility(newVisibility);
needsLayout = true;
}
if (mState.strengthId != state.strengthId) {
@@ -252,7 +254,7 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
@Override
public boolean isIconVisible() {
return mState.visible;
return mState.visible && !mForceHidden;
}
@Override
@@ -279,6 +281,23 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
}
}
/**
* Forces the state to be hidden (views will be GONE) and if necessary updates the layout.
*
* Makes sure that the {@link StatusBarIconController} cannot make it visible while this flag
* is enabled.
* @param forceHidden {@code true} if the icon should be GONE in its view regardless of its
* state.
* {@code false} if the icon should show as determined by its controller.
*/
public void forceHidden(boolean forceHidden) {
if (mForceHidden != forceHidden) {
mForceHidden = forceHidden;
updateState(mState);
requestLayout();
}
}
@Override
public int getVisibleState() {
return mVisibleState;

View File

@@ -33,6 +33,8 @@ import android.view.View;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import com.android.systemui.util.leak.RotationUtils;
/**
@@ -91,6 +93,7 @@ public class StatusBarWindowView extends FrameLayout {
* @param roundedCornerContentPadding
* @return
*/
@NonNull
public static Pair<Integer, Integer> paddingNeededForCutoutAndRoundedCorner(
DisplayCutout cutout, Pair<Integer, Integer> cornerCutoutPadding,
int roundedCornerContentPadding) {

View File

@@ -261,6 +261,25 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout {
addIgnoredSlots(slots);
}
/**
* Returns the view corresponding to a particular slot.
*
* Use it solely to manipulate how it is presented.
* @param slot name of the slot to find. Names are defined in
* {@link com.android.internal.R.config_statusBarIcons}
* @return a view for the slot if this container has it, else {@code null}
*/
public View getViewForSlot(String slot) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child instanceof StatusIconDisplayable
&& ((StatusIconDisplayable) child).getSlot().equals(slot)) {
return child;
}
}
return null;
}
/**
* Layout is happening from end -> start
*/

View File

@@ -21,9 +21,9 @@ import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.colorextraction.SysuiColorExtractor
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.colorextraction.SysuiColorExtractor
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.OngoingPrivacyChip
@@ -32,16 +32,12 @@ import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.settings.UserTracker
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.NextAlarmController
import com.android.systemui.util.RingerModeTracker
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.utils.leaks.FakeZenModeController
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -61,26 +57,16 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
@Mock
private lateinit var view: QuickStatusBarHeader
@Mock
private lateinit var zenModeController: FakeZenModeController
@Mock
private lateinit var nextAlarmController: NextAlarmController
@Mock
private lateinit var privacyItemController: PrivacyItemController
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var ringerModeTracker: RingerModeTracker
@Mock
private lateinit var activityStarter: ActivityStarter
@Mock
private lateinit var uiEventLogger: UiEventLogger
@Mock
private lateinit var qsTileHost: QSTileHost
@Mock
private lateinit var statusBarIconController: StatusBarIconController
@Mock
private lateinit var demoModeController: DemoModeController
@Mock
private lateinit var userTracker: UserTracker
@Mock
private lateinit var quickQSPanelController: QuickQSPanelController
@Mock(answer = Answers.RETURNS_SELF)
private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
@@ -105,6 +91,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var context: Context
private val qsExpansionPathInterpolator = QSExpansionPathInterpolator()
private lateinit var controller: QuickStatusBarHeaderController
@Before
@@ -119,21 +107,17 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
controller = QuickStatusBarHeaderController(
view,
zenModeController,
nextAlarmController,
privacyItemController,
ringerModeTracker,
activityStarter,
uiEventLogger,
qsTileHost,
statusBarIconController,
demoModeController,
userTracker,
quickQSPanelController,
qsCarrierGroupControllerBuilder,
privacyLogger,
colorExtractor,
privacyDialogController
privacyDialogController,
qsExpansionPathInterpolator
)
}