diff options
36 files changed, 825 insertions, 439 deletions
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index 85a9fec859f3..fffcafbf88fb 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -34,7 +34,7 @@ public interface QS extends FragmentBase { String ACTION = "com.android.systemui.action.PLUGIN_QS"; - int VERSION = 7; + int VERSION = 8; String TAG = "QS"; @@ -67,15 +67,12 @@ public interface QS extends FragmentBase { } /** - * We need this to handle nested scrolling for QS.. - * Normally we would do this with requestDisallowInterceptTouchEvent, but when both the - * scroll containers are using the same touch slop, they try to start scrolling at the - * same time and NotificationPanelView wins, this lets QS win. - * - * TODO: Do this using NestedScroll capabilities. + * Should touches from the notification panel be disallowed? + * The notification panel might grab any touches rom QS at any time to collapse the shade. + * We should disallow that in case we are showing the detail panel. */ - default boolean onInterceptTouchEvent(MotionEvent event) { - return isCustomizing(); + default boolean disallowPanelTouches() { + return isShowingDetail(); } @ProvidesInterface(version = HeightListener.VERSION) diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml index 294bd50fcf8b..59e1a755d7d2 100644 --- a/packages/SystemUI/res/layout/qs_detail.xml +++ b/packages/SystemUI/res/layout/qs_detail.xml @@ -22,6 +22,7 @@ android:background="@drawable/qs_detail_background" android:clickable="true" android:orientation="vertical" + android:layout_marginTop="@*android:dimen/quick_qs_offset_height" android:paddingBottom="8dp" android:visibility="invisible" android:elevation="4dp" @@ -44,7 +45,7 @@ android:scaleType="fitXY" /> - <com.android.systemui.qs.NonInterceptingScrollView + <ScrollView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" @@ -54,7 +55,7 @@ android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent"/> - </com.android.systemui.qs.NonInterceptingScrollView> + </ScrollView> <include layout="@layout/qs_detail_buttons" /> diff --git a/packages/SystemUI/res/layout/qs_divider.xml b/packages/SystemUI/res/layout/qs_divider.xml deleted file mode 100644 index 39d48ea4746e..000000000000 --- a/packages/SystemUI/res/layout/qs_divider.xml +++ /dev/null @@ -1,20 +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. ---> -<View xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="1dp" - android:alpha=".12" - android:background="?android:attr/colorForeground" /> diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index ebfd0a0fd537..5c00af5705e9 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -61,7 +61,10 @@ android:clickable="true" android:gravity="center_vertical" android:focusable="true" + android:singleLine="true" + android:ellipsize="end" android:textAppearance="@style/TextAppearance.QS.Status" + android:layout_marginEnd="4dp" android:visibility="gone"/> </com.android.keyguard.AlphaOptimizedLinearLayout> diff --git a/packages/SystemUI/res/layout/qs_paged_page.xml b/packages/SystemUI/res/layout/qs_paged_page.xml index a8960d9b9437..5c8b2b08324f 100644 --- a/packages/SystemUI/res/layout/qs_paged_page.xml +++ b/packages/SystemUI/res/layout/qs_paged_page.xml @@ -20,7 +20,5 @@ android:id="@+id/tile_page" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingStart="@dimen/notification_side_paddings" - android:paddingEnd="@dimen/notification_side_paddings" android:clipChildren="false" android:clipToPadding="false" /> diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index cdf84260e399..761ab03ee87e 100644 --- a/packages/SystemUI/res/layout/qs_panel.xml +++ b/packages/SystemUI/res/layout/qs_panel.xml @@ -48,18 +48,22 @@ android:clipChildren="false" android:background="@drawable/qs_bg_gradient" /> - - <com.android.systemui.qs.QSPanel - android:id="@+id/quick_settings_panel" - android:layout_marginTop="@*android:dimen/quick_qs_offset_height" + <com.android.systemui.qs.NonInterceptingScrollView + android:id="@+id/expanded_qs_scroll_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:elevation="4dp" - android:background="@android:color/transparent" - android:focusable="true" - android:accessibilityTraversalBefore="@android:id/edit"> - <include layout="@layout/qs_footer_impl" /> - </com.android.systemui.qs.QSPanel> + android:layout_weight="1"> + <com.android.systemui.qs.QSPanel + android:id="@+id/quick_settings_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:focusable="true" + android:accessibilityTraversalBefore="@android:id/edit"> + <include layout="@layout/qs_footer_impl" /> + </com.android.systemui.qs.QSPanel> + </com.android.systemui.qs.NonInterceptingScrollView> <include layout="@layout/quick_status_bar_expanded_header" /> diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml index 15f398aa52e6..e7c7b5fbf890 100644 --- a/packages/SystemUI/res/layout/quick_settings_footer.xml +++ b/packages/SystemUI/res/layout/quick_settings_footer.xml @@ -14,8 +14,8 @@ 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:layout_width="match_parent" +<com.android.systemui.util.NeverExactlyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:clickable="true" android:paddingBottom="@dimen/qs_tile_padding_top" @@ -23,23 +23,28 @@ android:paddingStart="@dimen/qs_footer_padding_start" android:paddingEnd="@dimen/qs_footer_padding_end" android:gravity="center_vertical" + android:layout_gravity="center_vertical|center_horizontal" android:background="@android:color/transparent"> - <TextView + <com.android.systemui.util.AutoMarqueeTextView android:id="@+id/footer_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="start" android:layout_weight="1" + android:singleLine="true" + android:ellipsize="marquee" + android:marqueeRepeatLimit="marquee_forever" android:textAppearance="@style/TextAppearance.QS.TileLabel" - style="@style/qs_security_footer"/> + android:textColor="?android:attr/textColorPrimary"/> <ImageView android:id="@+id/footer_icon" android:layout_width="@dimen/qs_footer_icon_size" android:layout_height="@dimen/qs_footer_icon_size" + android:layout_marginStart="8dp" android:contentDescription="@null" android:src="@drawable/ic_info_outline" - style="@style/qs_security_footer"/> + android:tint="?android:attr/textColorPrimary" /> -</LinearLayout> +</com.android.systemui.util.NeverExactlyLinearLayout> diff --git a/packages/SystemUI/res/layout/quick_settings_header_info.xml b/packages/SystemUI/res/layout/quick_settings_header_info.xml index e6ef9b4b902c..fb82304663aa 100644 --- a/packages/SystemUI/res/layout/quick_settings_header_info.xml +++ b/packages/SystemUI/res/layout/quick_settings_header_info.xml @@ -20,11 +20,12 @@ 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:theme="@style/QSHeaderTheme" + android:forceHasOverlappingRendering="false"> <com.android.systemui.qs.QSHeaderInfoLayout android:id="@+id/status_container" - android:layout_width="match_parent" + android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent"> diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml index abeb33111c40..dc34127496f6 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml @@ -29,7 +29,6 @@ android:clipToPadding="false" android:paddingTop="0dp" android:paddingEnd="0dp" - android:paddingBottom="10dp" android:paddingStart="0dp" android:elevation="4dp" > @@ -52,6 +51,7 @@ android:clipChildren="false" android:clipToPadding="false" android:focusable="true" + android:paddingBottom="10dp" android:importantForAccessibility="yes" /> <TextView diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index d118d8956f17..2d42ce6faa2c 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -31,7 +31,6 @@ <dimen name="battery_detail_graph_space_bottom">9dp</dimen> <integer name="quick_settings_num_columns">4</integer> - <bool name="quick_settings_wide">true</bool> <dimen name="qs_detail_margin_top">0dp</dimen> <dimen name="volume_tool_tip_right_margin">136dp</dimen> diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml index 50261e1b2139..4fdeb6fa4a92 100644 --- a/packages/SystemUI/res/values-night/styles.xml +++ b/packages/SystemUI/res/values-night/styles.xml @@ -29,9 +29,4 @@ <item name="android:textColor">?android:attr/textColorPrimary</item> </style> - <style name="qs_security_footer" parent="@style/qs_theme"> - <item name="android:textColor">#B3FFFFFF</item> <!-- 70% white --> - <item name="android:tint">#FFFFFFFF</item> - </style> - </resources> diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index c4c467152281..3b00ad1bf0c4 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -15,8 +15,5 @@ ~ limitations under the License --> <resources> - <!-- Whether QuickSettings is in a phone landscape --> - <bool name="quick_settings_wide">false</bool> - <integer name="quick_settings_num_columns">3</integer> </resources> diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml new file mode 100644 index 000000000000..40838f362f5c --- /dev/null +++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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. + --> +<resources> + <!-- Size of the panel of large phones on portrait. This shouldn't fill, but have some padding on the side --> + <dimen name="notification_panel_width">416dp</dimen> + +</resources> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 8f73d231c732..fdf4e3b1b796 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -16,8 +16,6 @@ */ --> <resources> - <!-- Standard notification width + gravity --> - <dimen name="notification_panel_width">416dp</dimen> <!-- Diameter of outer shape drawable shown in navbar search--> <dimen name="navbar_search_outerring_diameter">430dip</dimen> diff --git a/packages/SystemUI/res/values-sw900dp-land/dimen.xml b/packages/SystemUI/res/values-sw900dp-land/dimen.xml deleted file mode 100644 index 1e0600ed5fe0..000000000000 --- a/packages/SystemUI/res/values-sw900dp-land/dimen.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (c) 2012, 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. -*/ ---> -<resources> - <!-- Standard notification width + gravity for tablet large screen device --> - <dimen name="notification_panel_width">544dp</dimen> - -</resources> - diff --git a/packages/SystemUI/res/values-w550dp-land/config.xml b/packages/SystemUI/res/values-w550dp-land/config.xml index 16d5317636a2..a33f1312521f 100644 --- a/packages/SystemUI/res/values-w550dp-land/config.xml +++ b/packages/SystemUI/res/values-w550dp-land/config.xml @@ -20,9 +20,5 @@ <!-- These resources are around just to allow their values to be customized for different hardware and product builds. --> <resources> - - <!-- Whether QuickSettings is in a phone landscape --> - <bool name="quick_settings_wide">true</bool> - - <integer name="quick_settings_num_columns">4</integer> + <integer name="quick_settings_num_columns">6</integer> </resources> diff --git a/packages/SystemUI/res/values-w550dp-land/dimens.xml b/packages/SystemUI/res/values-w550dp-land/dimens.xml deleted file mode 100644 index 017ca6987820..000000000000 --- a/packages/SystemUI/res/values-w550dp-land/dimens.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (c) 2016, 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. -*/ ---> -<resources> - <!-- Standard notification width + gravity --> - <dimen name="notification_panel_width">544dp</dimen> - -</resources> diff --git a/packages/SystemUI/res/values-w650dp-land/dimens.xml b/packages/SystemUI/res/values-w650dp-land/dimens.xml new file mode 100644 index 000000000000..108d6cf16fec --- /dev/null +++ b/packages/SystemUI/res/values-w650dp-land/dimens.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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. + --> +<resources> + <!-- Standard notification width + gravity --> + <dimen name="notification_panel_width">644dp</dimen> + +</resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 00537ff0466d..848cdb1e831c 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -95,9 +95,6 @@ <!-- The maximum number of tiles in the QuickQSPanel --> <integer name="quick_qs_panel_max_columns">6</integer> - <!-- Whether QuickSettings is in a phone landscape --> - <bool name="quick_settings_wide">false</bool> - <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">3</integer> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 73d8e9a0d8a7..ab0f876a68b8 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1075,8 +1075,7 @@ <dimen name="edge_margin">8dp</dimen> <!-- The absolute side margins of quick settings --> - <dimen name="quick_settings_expanded_bottom_margin">16dp</dimen> - <dimen name="quick_settings_media_extra_bottom_margin">6dp</dimen> + <dimen name="quick_settings_bottom_margin_media">16dp</dimen> <dimen name="rounded_corner_content_padding">0dp</dimen> <dimen name="nav_content_padding">0dp</dimen> <dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen> @@ -1268,8 +1267,8 @@ <dimen name="qs_media_panel_outer_padding">16dp</dimen> <dimen name="qs_media_album_size">52dp</dimen> <dimen name="qs_seamless_icon_size">20dp</dimen> - <dimen name="qqs_media_spacing">8dp</dimen> - <dimen name="qqs_horizonal_tile_padding_bottom">8dp</dimen> + <dimen name="qqs_media_spacing">16dp</dimen> + <dimen name="qs_footer_horizontal_margin">22dp</dimen> <dimen name="magnification_border_size">5dp</dimen> <dimen name="magnification_frame_move_short">5dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 39f78bf46028..ed36bdbe1e7e 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -387,11 +387,6 @@ <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item> </style> - <style name="qs_security_footer" parent="@style/qs_theme"> - <item name="android:textColor">?android:attr/textColorSecondary</item> - <item name="android:tint">?android:attr/textColorSecondary</item> - </style> - <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light"> <item name="android:colorAccent">@color/remote_input_accent</item> </style> diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 775a1649702a..b86e1d0503d4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -480,7 +480,17 @@ class MediaHierarchyManager @Inject constructor( if (inOverlay) { rootOverlay!!.add(mediaFrame) } else { + // When adding back to the host, let's make sure to reset the bounds. + // Usually adding the view will trigger a layout that does this automatically, + // but we sometimes suppress this. targetHost.addView(mediaFrame) + val left = targetHost.paddingLeft + val top = targetHost.paddingTop + mediaFrame.setLeftTopRightBottom( + left, + top, + left + currentBounds.width(), + top + currentBounds.height()) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt index 40d317c7bb22..dc157a8dd257 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt @@ -139,12 +139,7 @@ class DoubleLineTileLayout( } tilesToShow = actualColumns * NUM_LINES - val interTileSpace = if (actualColumns <= 2) { - // Extra "column" of padding to be distributed on each end - (availableWidth - actualColumns * smallTileSize) / actualColumns - } else { - (availableWidth - actualColumns * smallTileSize) / (actualColumns - 1) - } + val spacePerTile = availableWidth / actualColumns for (index in 0 until mRecords.size) { val tileView = mRecords[index].tileView @@ -154,15 +149,16 @@ class DoubleLineTileLayout( tileView.visibility = View.VISIBLE if (index > 0) tileView.updateAccessibilityOrder(mRecords[index - 1].tileView) val column = index % actualColumns - val left = getLeftForColumn(column, interTileSpace, actualColumns <= 2) + val left = getLeftForColumn(column, spacePerTile) val top = if (index < actualColumns) 0 else getTopBottomRow() tileView.layout(left, top, left + smallTileSize, top + smallTileSize) } } } - private fun getLeftForColumn(column: Int, interSpace: Int, sideMargin: Boolean): Int { - return (if (sideMargin) interSpace / 2 else 0) + column * (smallTileSize + interSpace) + private fun getLeftForColumn(column: Int, spacePerTile: Int): Int { + // Distribute the space evenly among all tiles. + return (column * spacePerTile + spacePerTile / 2.0f - smallTileSize / 2.0f).toInt() } private fun getTopBottomRow() = smallTileSize + cellMarginVertical diff --git a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java index c204d94916a4..aa17c4aa79b1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java @@ -17,6 +17,9 @@ package com.android.systemui.qs; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewParent; import android.widget.ScrollView; /** @@ -24,8 +27,12 @@ import android.widget.ScrollView; */ public class NonInterceptingScrollView extends ScrollView { + private final int mTouchSlop; + private float mDownY; + public NonInterceptingScrollView(Context context, AttributeSet attrs) { super(context, attrs); + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } @Override @@ -34,10 +41,52 @@ public class NonInterceptingScrollView extends ScrollView { switch (action) { case MotionEvent.ACTION_DOWN: if (canScrollVertically(1)) { - requestDisallowInterceptTouchEvent(true); + // If we can scroll down, make sure we're not intercepted by the parent + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } } break; } return super.onTouchEvent(ev); } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + // If there's a touch on this view and we can scroll down, we don't want to be intercepted + int action = ev.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: + // If we can scroll down, make sure non of our parents intercepts us. + if (canScrollVertically(1)) { + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + } + mDownY = ev.getY(); + break; + case MotionEvent.ACTION_MOVE: { + final int y = (int) ev.getY(); + final float yDiff = y - mDownY; + if (yDiff < -mTouchSlop && !canScrollVertically(1)) { + // Don't intercept touches that are overscrolling. + return false; + } + break; + } + } + return super.onInterceptTouchEvent(ev); + } + + public int getScrollRange() { + int scrollRange = 0; + if (getChildCount() > 0) { + View child = getChildAt(0); + scrollRange = Math.max(0, + child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop)); + } + return scrollRange; + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index aaff9ac47ebf..28369925367a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -66,6 +66,10 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private int mHorizontalClipBound; private final Rect mClippingRect; private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger(); + private int mExcessHeight; + private int mLastExcessHeight; + private int mMinRows = 1; + private int mMaxColumns = TileLayout.NO_MAX_COLUMNS; public PagedTileLayout(Context context, AttributeSet attrs) { super(context, attrs); @@ -195,11 +199,18 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); - mPages.add((TilePage) LayoutInflater.from(getContext()) - .inflate(R.layout.qs_paged_page, this, false)); + mPages.add(createTilePage()); mAdapter.notifyDataSetChanged(); } + private TilePage createTilePage() { + TilePage page = (TilePage) LayoutInflater.from(getContext()) + .inflate(R.layout.qs_paged_page, this, false); + page.setMinRows(mMinRows); + page.setMaxColumns(mMaxColumns); + return page; + } + public void setPageIndicator(PageIndicator indicator) { mPageIndicator = indicator; mPageIndicator.setNumPages(mPages.size()); @@ -298,8 +309,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } while (mPages.size() < numPages) { if (DEBUG) Log.d(TAG, "Adding page"); - mPages.add((TilePage) LayoutInflater.from(getContext()) - .inflate(R.layout.qs_paged_page, this, false)); + mPages.add(createTilePage()); } while (mPages.size() > numPages) { if (DEBUG) Log.d(TAG, "Removing page"); @@ -342,17 +352,54 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } @Override + public boolean setMinRows(int minRows) { + mMinRows = minRows; + boolean changed = false; + for (int i = 0; i < mPages.size(); i++) { + if (mPages.get(i).setMinRows(minRows)) { + changed = true; + mDistributeTiles = true; + } + } + return changed; + } + + @Override + public boolean setMaxColumns(int maxColumns) { + mMaxColumns = maxColumns; + boolean changed = false; + for (int i = 0; i < mPages.size(); i++) { + if (mPages.get(i).setMaxColumns(maxColumns)) { + changed = true; + mDistributeTiles = true; + } + } + return changed; + } + + /** + * Set the amount of excess space that we gave this view compared to the actual available + * height. This is because this view is in a scrollview. + */ + public void setExcessHeight(int excessHeight) { + mExcessHeight = excessHeight; + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int nTiles = mTiles.size(); // If we have no reason to recalculate the number of rows, skip this step. In particular, // if the height passed by its parent is the same as the last time, we try not to remeasure. - if (mDistributeTiles || mLastMaxHeight != MeasureSpec.getSize(heightMeasureSpec)) { + if (mDistributeTiles || mLastMaxHeight != MeasureSpec.getSize(heightMeasureSpec) + || mLastExcessHeight != mExcessHeight) { mLastMaxHeight = MeasureSpec.getSize(heightMeasureSpec); + mLastExcessHeight = mExcessHeight; // Only change the pages if the number of rows or columns (from updateResources) has // changed or the tiles have changed - if (mPages.get(0).updateMaxRows(heightMeasureSpec, nTiles) || mDistributeTiles) { + int availableHeight = mLastMaxHeight - mExcessHeight; + if (mPages.get(0).updateMaxRows(availableHeight, nTiles) || mDistributeTiles) { mDistributeTiles = false; distributeTiles(); } @@ -485,14 +532,6 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { // up. return Math.max(mColumns * mRows, 1); } - - @Override - public boolean updateResources() { - final int sidePadding = getContext().getResources().getDimensionPixelSize( - R.dimen.notification_side_paddings); - setPadding(sidePadding, 0, sidePadding, 0); - return super.updateResources(); - } } private final PagerAdapter mAdapter = new PagerAdapter() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index ce002297e1a1..bc8f5a8fb652 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -18,6 +18,7 @@ import android.util.Log; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.View.OnLayoutChangeListener; +import android.widget.ScrollView; import com.android.systemui.Dependency; import com.android.systemui.plugins.qs.QS; @@ -30,7 +31,6 @@ import com.android.systemui.qs.TouchAnimator.Builder; import com.android.systemui.qs.TouchAnimator.Listener; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; -import com.android.systemui.util.Utils; import java.util.ArrayList; import java.util.Collection; @@ -66,6 +66,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private TouchAnimator mNonfirstPageAnimator; private TouchAnimator mNonfirstPageDelayedAnimator; private TouchAnimator mBrightnessAnimator; + private boolean mNeedsAnimatorUpdate = false; private boolean mOnKeyguard; @@ -98,6 +99,12 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha updateAnimators(); } + + public void onQsScrollingChanged() { + // Lazily update animators whenever the scrolling changes + mNeedsAnimatorUpdate = true; + } + public void setOnKeyguard(boolean onKeyguard) { mOnKeyguard = onKeyguard; updateQQSVisibility(); @@ -172,6 +179,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } private void updateAnimators() { + mNeedsAnimatorUpdate = false; TouchAnimator.Builder firstPageBuilder = new Builder(); TouchAnimator.Builder translationXBuilder = new Builder(); TouchAnimator.Builder translationYBuilder = new Builder(); @@ -286,13 +294,16 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha .setListener(this) .build(); // Fade in the tiles/labels as we reach the final position. - mFirstPageDelayedAnimator = new TouchAnimator.Builder() + Builder builder = new Builder() .setStartDelay(EXPANDED_TILE_DELAY) - .addFloat(tileLayout, "alpha", 0, 1) - .addFloat(mQsPanel.getDivider(), "alpha", 0, 1) - .addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build(); - mAllViews.add(mQsPanel.getDivider()); - mAllViews.add(mQsPanel.getFooter().getView()); + .addFloat(tileLayout, "alpha", 0, 1); + if (mQsPanel.getSecurityFooter() != null) { + builder.addFloat(mQsPanel.getSecurityFooter().getView(), "alpha", 0, 1); + } + mFirstPageDelayedAnimator = builder.build(); + if (mQsPanel.getSecurityFooter() != null) { + mAllViews.add(mQsPanel.getSecurityFooter().getView()); + } float px = 0; float py = 1; if (tiles.size() <= 3) { @@ -308,7 +319,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } mNonfirstPageAnimator = new TouchAnimator.Builder() .addFloat(mQuickQsPanel, "alpha", 1, 0) - .addFloat(mQsPanel.getDivider(), "alpha", 0, 1) .setListener(mNonFirstPageListener) .setEndDelay(.5f) .build(); @@ -339,10 +349,18 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha loc1[0] += view.getLeft(); loc1[1] += view.getTop(); } + if (!(view instanceof PagedTileLayout)) { + // Remove the scrolling position of all scroll views other than the viewpager + loc1[0] -= view.getScrollX(); + loc1[1] -= view.getScrollY(); + } getRelativePositionInt(loc1, (View) view.getParent(), parent); } public void setPosition(float position) { + if (mNeedsAnimatorUpdate) { + updateAnimators(); + } if (mFirstPageAnimator == null) return; if (mOnKeyguard) { if (mShowCollapsedOnKeyguard) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 1c3b6850afc1..0332bc3e0618 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -43,6 +43,7 @@ public class QSContainerImpl extends FrameLayout { private float mQsExpansion; private QSCustomizer mQSCustomizer; private View mDragHandle; + private View mQSPanelContainer; private View mBackground; private View mBackgroundGradient; @@ -61,6 +62,7 @@ public class QSContainerImpl extends FrameLayout { protected void onFinishInflate() { super.onFinishInflate(); mQSPanel = findViewById(R.id.quick_settings_panel); + mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view); mQSDetail = findViewById(R.id.qs_detail); mHeader = findViewById(R.id.header); mQSCustomizer = findViewById(R.id.qs_customize); @@ -95,7 +97,7 @@ public class QSContainerImpl extends FrameLayout { Configuration config = getResources().getConfiguration(); boolean navBelow = config.smallestScreenWidthDp >= 600 || config.orientation != Configuration.ORIENTATION_LANDSCAPE; - MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanel.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams(); // The footer is pinned to the bottom of QSPanel (same bottoms), therefore we don't need to // subtract its height. We do not care if the collapsed notifications fit in the screen. @@ -109,12 +111,11 @@ public class QSContainerImpl extends FrameLayout { + layoutParams.rightMargin; final int qsPanelWidthSpec = getChildMeasureSpec(widthMeasureSpec, padding, layoutParams.width); - // Measure with EXACTLY. That way, PagedTileLayout will only use excess height and will be - // measured last, after other views and padding is accounted for. - mQSPanel.measure(qsPanelWidthSpec, MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.EXACTLY)); - int width = mQSPanel.getMeasuredWidth() + padding; + mQSPanelContainer.measure(qsPanelWidthSpec, + MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST)); + int width = mQSPanelContainer.getMeasuredWidth() + padding; int height = layoutParams.topMargin + layoutParams.bottomMargin - + mQSPanel.getMeasuredHeight() + getPaddingBottom(); + + mQSPanelContainer.getMeasuredHeight() + getPaddingBottom(); super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); // QSCustomizer will always be the height of the screen, but do this after @@ -130,7 +131,7 @@ public class QSContainerImpl extends FrameLayout { // Do not measure QSPanel again when doing super.onMeasure. // This prevents the pages in PagedTileLayout to be remeasured with a different (incorrect) // size to the one used for determining the number of rows and then the number of pages. - if (child != mQSPanel) { + if (child != mQSPanelContainer) { super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed); } @@ -151,10 +152,10 @@ public class QSContainerImpl extends FrameLayout { } private void updateResources() { - LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams(); + LayoutParams layoutParams = (LayoutParams) mQSPanelContainer.getLayoutParams(); layoutParams.topMargin = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.quick_qs_offset_height); - mQSPanel.setLayoutParams(layoutParams); + mQSPanelContainer.setLayoutParams(layoutParams); mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); mContentPaddingStart = getResources().getDimensionPixelSize( @@ -185,7 +186,7 @@ public class QSContainerImpl extends FrameLayout { mQSDetail.setBottom(getTop() + height); // Pin the drag handle to the bottom of the panel. mDragHandle.setTranslationY(height - mDragHandle.getHeight()); - mBackground.setTop(mQSPanel.getTop()); + mBackground.setTop(mQSPanelContainer.getTop()); mBackground.setBottom(height); } @@ -223,7 +224,7 @@ public class QSContainerImpl extends FrameLayout { LayoutParams lp = (LayoutParams) view.getLayoutParams(); lp.rightMargin = mSideMargins; lp.leftMargin = mSideMargins; - if (view == mQSPanel) { + if (view == mQSPanelContainer) { // QS panel lays out some of its content full width mQSPanel.setContentMargins(mContentPaddingStart, mContentPaddingEnd); } else if (view == mHeader) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index dd02edd506b8..f1bb8996e181 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -24,7 +24,6 @@ import android.os.Bundle; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -72,6 +71,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca protected QuickStatusBarHeader mHeader; private QSCustomizer mQSCustomizer; protected QSPanel mQSPanel; + protected NonInterceptingScrollView mQSPanelScrollView; private QSDetail mQSDetail; private boolean mListening; private QSContainerImpl mContainer; @@ -122,8 +122,20 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mQSPanel = view.findViewById(R.id.quick_settings_panel); + mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view); + mQSPanelScrollView.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + updateQsBounds(); + }); + mQSPanelScrollView.setOnScrollChangeListener( + (v, scrollX, scrollY, oldScrollX, oldScrollY) -> { + // Lazily update animators whenever the scrolling changes + mQSAnimator.onQsScrollingChanged(); + mHeader.setExpandedScrollAmount(scrollY); + }); mQSDetail = view.findViewById(R.id.qs_detail); mHeader = view.findViewById(R.id.header); + mQSPanel.setHeaderContainer(view.findViewById(R.id.header_text_container)); mFooter = view.findViewById(R.id.qs_footer); mContainer = view.findViewById(id.quick_settings_container); @@ -133,8 +145,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter); - mQSAnimator = new QSAnimator(this, - mHeader.findViewById(R.id.quick_qs_panel), mQSPanel); + mQSAnimator = new QSAnimator(this, mHeader.findViewById(R.id.quick_qs_panel), mQSPanel); + mQSCustomizer = view.findViewById(R.id.qs_customize); mQSCustomizer.setQs(this); @@ -319,11 +331,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - return isCustomizing(); - } - - @Override public void setHeaderClickable(boolean clickable) { if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable); } @@ -394,7 +401,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mLastViewHeight = currentHeight; boolean fullyExpanded = expansion == 1; - int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom(); + boolean fullyCollapsed = expansion == 0.0f; + int heightDiff = mQSPanelScrollView.getBottom() - mHeader.getBottom() + + mHeader.getPaddingBottom(); float panelTranslationY = translationScaleY * heightDiff; // Let the views animate their contents correctly by giving them the necessary context. @@ -403,19 +412,19 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion); mQSPanel.getQsTileRevealController().setExpansion(expansion); mQSPanel.getTileLayout().setExpansion(expansion); - mQSPanel.setTranslationY(translationScaleY * heightDiff); + mQSPanelScrollView.setTranslationY(translationScaleY * heightDiff); + if (fullyCollapsed) { + mQSPanelScrollView.setScrollY(0); + } mQSDetail.setFullyExpanded(fullyExpanded); - if (fullyExpanded) { - // Always draw within the bounds of the view when fully expanded. - mQSPanel.setClipBounds(null); - } else { + if (!fullyExpanded) { // Set bounds on the QS panel so it doesn't run over the header when animating. - mQsBounds.top = (int) -mQSPanel.getTranslationY(); - mQsBounds.right = mQSPanel.getWidth(); - mQsBounds.bottom = mQSPanel.getHeight(); - mQSPanel.setClipBounds(mQsBounds); + mQsBounds.top = (int) -mQSPanelScrollView.getTranslationY(); + mQsBounds.right = mQSPanelScrollView.getWidth(); + mQsBounds.bottom = mQSPanelScrollView.getHeight(); } + updateQsBounds(); if (mQSAnimator != null) { mQSAnimator.setPosition(expansion); @@ -423,24 +432,34 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca updateMediaPositions(); } + private void updateQsBounds() { + if (mLastQSExpansion == 1.0f) { + // Fully expanded, let's set the layout bounds as clip bounds. This is necessary because + // it's a scrollview and otherwise wouldn't be clipped. + mQsBounds.set(0, 0, mQSPanelScrollView.getWidth(), mQSPanelScrollView.getHeight()); + } + mQSPanelScrollView.setClipBounds(mQsBounds); + } + private void updateMediaPositions() { if (Utils.useQsMediaPlayer(getContext())) { mContainer.getLocationOnScreen(mTmpLocation); float absoluteBottomPosition = mTmpLocation[1] + mContainer.getHeight(); + // The Media can be scrolled off screen by default, let's offset it + float expandedMediaPosition = absoluteBottomPosition - mQSPanelScrollView.getScrollY() + + mQSPanelScrollView.getScrollRange(); // The expanded media host should never move below the laid out position - pinToBottom(absoluteBottomPosition, mQSPanel.getMediaHost(), true /* expanded */); + pinToBottom(expandedMediaPosition, mQSPanel.getMediaHost(), true /* expanded */); // The expanded media host should never move above the laid out position - pinToBottom(absoluteBottomPosition - mHeader.getPaddingBottom(), - mHeader.getHeaderQsPanel().getMediaHost(), false /* expanded */); + pinToBottom(absoluteBottomPosition, mHeader.getHeaderQsPanel().getMediaHost(), + false /* expanded */); } } private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded) { View hostView = mediaHost.getHostView(); if (mLastQSExpansion > 0) { - ViewGroup.MarginLayoutParams params = - (ViewGroup.MarginLayoutParams) hostView.getLayoutParams(); - float targetPosition = absoluteBottomPosition - params.bottomMargin + float targetPosition = absoluteBottomPosition - getTotalBottomMargin(hostView) - hostView.getHeight(); float currentPosition = mediaHost.getCurrentBounds().top - hostView.getTranslationY(); @@ -458,6 +477,18 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } } + private float getTotalBottomMargin(View startView) { + int result = 0; + View child = startView; + View parent = (View) startView.getParent(); + while (!(parent instanceof QSContainerImpl) && parent != null) { + result += parent.getHeight() - child.getBottom(); + child = parent; + parent = (View) parent.getParent(); + } + return result; + } + private boolean headerWillBeAnimating() { return mState == StatusBarState.KEYGUARD && mShowCollapsedOnKeyguard && !isKeyguardShowing(); @@ -514,7 +545,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void notifyCustomizeChanged() { // The customize state changed, so our height changed. mContainer.updateExpansion(); - mQSPanel.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE); + mQSPanelScrollView.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE + : View.INVISIBLE); mFooter.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE); // Let the panel know the position changed and it needs to update where notifications // and whatnot are. @@ -531,9 +563,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca return getView().getHeight(); } if (mQSDetail.isClosingDetail()) { - LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams(); + LayoutParams layoutParams = (LayoutParams) mQSPanelScrollView.getLayoutParams(); int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin + - + mQSPanel.getMeasuredHeight(); + + mQSPanelScrollView.getMeasuredHeight(); return panelHeight + getView().getPaddingBottom(); } else { return getView().getMeasuredHeight(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 78448785fe2f..ecdb2c91ca48 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -16,10 +16,10 @@ package com.android.systemui.qs; -import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState; import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; import static com.android.systemui.util.Utils.useQsMediaPlayer; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; @@ -29,8 +29,8 @@ import android.metrics.LogMaker; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.service.quicksettings.Tile; import android.util.AttributeSet; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -39,7 +39,7 @@ import android.widget.LinearLayout; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settingslib.Utils; +import com.android.internal.widget.RemeasuringLinearLayout; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.R; @@ -83,38 +83,65 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne protected final ArrayList<TileRecord> mRecords = new ArrayList<>(); private final BroadcastDispatcher mBroadcastDispatcher; protected final MediaHost mMediaHost; + + /** + * The index where the content starts that needs to be moved between parents + */ + private final int mMovableContentStartIndex; private String mCachedSpecs = ""; - protected final View mBrightnessView; + + @Nullable + protected View mBrightnessView; + @Nullable + private BrightnessController mBrightnessController; + private final H mHandler = new H(); private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); - private final QSTileRevealController mQsTileRevealController; + private QSTileRevealController mQsTileRevealController; /** Whether or not the QS media player feature is enabled. */ protected boolean mUsingMediaPlayer; + private int mVisualMarginStart; + private int mVisualMarginEnd; protected boolean mExpanded; protected boolean mListening; private QSDetail.Callback mCallback; - private BrightnessController mBrightnessController; private final DumpManager mDumpManager; private final QSLogger mQSLogger; protected final UiEventLogger mUiEventLogger; protected QSTileHost mHost; - protected QSSecurityFooter mFooter; + @Nullable + protected QSSecurityFooter mSecurityFooter; + + @Nullable + protected View mFooter; + + @Nullable + private ViewGroup mHeaderContainer; private PageIndicator mFooterPageIndicator; private boolean mGridContentVisible = true; private int mContentMarginStart; private int mContentMarginEnd; private int mVisualTilePadding; - - protected QSTileLayout mTileLayout; + private boolean mUsingHorizontalLayout; private QSCustomizer mCustomizePanel; private Record mDetailRecord; private BrightnessMirrorController mBrightnessMirrorController; - private View mDivider; + private LinearLayout mHorizontalLinearLayout; + private LinearLayout mHorizontalContentContainer; + + // Only used with media + private QSTileLayout mHorizontalTileLayout; + protected QSTileLayout mRegularTileLayout; + protected QSTileLayout mTileLayout; + private int mLastOrientation = -1; + private int mMediaTotalBottomMargin; + private int mFooterMarginStartHorizontal; + @Inject public QSPanel( @@ -128,7 +155,13 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne ) { super(context, attrs); mUsingMediaPlayer = useQsMediaPlayer(context); + mMediaTotalBottomMargin = getResources().getDimensionPixelSize( + R.dimen.quick_settings_bottom_margin_media); mMediaHost = mediaHost; + mMediaHost.setVisibleChangedListener((visible) -> { + switchTileLayout(); + return null; + }); mContext = context; mQSLogger = qsLogger; mDumpManager = dumpManager; @@ -137,71 +170,97 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne setOrientation(VERTICAL); - mBrightnessView = LayoutInflater.from(mContext).inflate( - R.layout.quick_settings_brightness_dialog, this, false); - addView(mBrightnessView); - - mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate( - R.layout.qs_paged_tile_layout, this, false); + addViewsAboveTiles(); + mMovableContentStartIndex = getChildCount(); + mRegularTileLayout = createRegularTileLayout(); + + if (mUsingMediaPlayer) { + mHorizontalLinearLayout = new RemeasuringLinearLayout(mContext); + mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL); + mHorizontalLinearLayout.setClipChildren(false); + mHorizontalLinearLayout.setClipToPadding(false); + + mHorizontalContentContainer = new RemeasuringLinearLayout(mContext); + mHorizontalContentContainer.setOrientation(LinearLayout.VERTICAL); + mHorizontalContentContainer.setClipChildren(false); + mHorizontalContentContainer.setClipToPadding(false); + + mHorizontalTileLayout = createHorizontalTileLayout(); + LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1); + int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing); + lp.setMarginStart(0); + lp.setMarginEnd(marginSize); + lp.gravity = Gravity.CENTER_VERTICAL; + mHorizontalLinearLayout.addView(mHorizontalContentContainer, lp); + + lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0, 1); + addView(mHorizontalLinearLayout, lp); + + initMediaHostState(); + } + addSecurityFooter(); + if (mRegularTileLayout instanceof PagedTileLayout) { + mQsTileRevealController = new QSTileRevealController(mContext, this, + (PagedTileLayout) mRegularTileLayout); + } mQSLogger.logAllTilesChangeListening(mListening, getDumpableTag(), mCachedSpecs); - mTileLayout.setListening(mListening); - addView((View) mTileLayout); - - mQsTileRevealController = new QSTileRevealController(mContext, this, - (PagedTileLayout) mTileLayout); - - addDivider(); - - mFooter = new QSSecurityFooter(this, context); - addView(mFooter.getView()); - updateResources(); + } + protected void addSecurityFooter() { + mSecurityFooter = new QSSecurityFooter(this, mContext); + } + + protected void addViewsAboveTiles() { + mBrightnessView = LayoutInflater.from(mContext).inflate( + R.layout.quick_settings_brightness_dialog, this, false); + addView(mBrightnessView); mBrightnessController = new BrightnessController(getContext(), findViewById(R.id.brightness_slider), mBroadcastDispatcher); } - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - // Add media carousel at the end - if (useQsMediaPlayer(getContext())) { - addMediaHostView(); + protected QSTileLayout createRegularTileLayout() { + if (mRegularTileLayout == null) { + mRegularTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate( + R.layout.qs_paged_tile_layout, this, false); } + return mRegularTileLayout; + } + + + protected QSTileLayout createHorizontalTileLayout() { + return createRegularTileLayout(); } - protected void addMediaHostView() { + protected void initMediaHostState() { mMediaHost.setExpansion(1.0f); mMediaHost.setShowsOnlyActiveMedia(false); mMediaHost.init(MediaHierarchyManager.LOCATION_QS); - ViewGroup hostView = mMediaHost.getHostView(); - addView(hostView); - int bottomPadding = getResources().getDimensionPixelSize( - R.dimen.quick_settings_expanded_bottom_margin); - MarginLayoutParams layoutParams = (MarginLayoutParams) hostView.getLayoutParams(); - layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; - layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; - layoutParams.bottomMargin = bottomPadding; - hostView.setLayoutParams(layoutParams); - updateMediaHostContentMargins(); - } - - protected void addDivider() { - mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false); - mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(), - getColorForState(mContext, Tile.STATE_ACTIVE))); - addView(mDivider); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + if (mTileLayout instanceof PagedTileLayout) { + // Allow the UI to be as big as it want's to, we're in a scroll view + int newHeight = 10000; + int availableHeight = MeasureSpec.getSize(heightMeasureSpec); + int excessHeight = newHeight - availableHeight; + // Measure with EXACTLY. That way, The content will only use excess height and will + // be measured last, after other views and padding is accounted for. This only + // works because our Layouts in here remeasure themselves with the exact content + // height. + heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY); + ((PagedTileLayout) mTileLayout).setExcessHeight(excessHeight); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + // We want all the logic of LinearLayout#onMeasure, and for it to assign the excess space // not used by the other children to PagedTileLayout. However, in this case, LinearLayout // assumes that PagedTileLayout would use all the excess space. This is not the case as // PagedTileLayout height is quantized (because it shows a certain number of rows). // Therefore, after everything is measured, we need to make sure that we add up the correct // total height - super.onMeasure(widthMeasureSpec, heightMeasureSpec); int height = getPaddingBottom() + getPaddingTop(); int numChildren = getChildCount(); for (int i = 0; i < numChildren; i++) { @@ -215,10 +274,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne setMeasuredDimension(getMeasuredWidth(), height); } - public View getDivider() { - return mDivider; - } - public QSTileRevealController getQsTileRevealController() { return mQsTileRevealController; } @@ -273,7 +328,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne @Override public void onTuningChanged(String key, String newValue) { - if (QS_SHOW_BRIGHTNESS.equals(key)) { + if (QS_SHOW_BRIGHTNESS.equals(key) && mBrightnessView != null) { updateViewVisibilityForTuningValue(mBrightnessView, newValue); } } @@ -316,6 +371,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne updateBrightnessMirror(); } + @Nullable View getBrightnessView() { return mBrightnessView; } @@ -328,7 +384,9 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne mHost = host; mHost.addCallback(this); setTiles(mHost.getTiles()); - mFooter.setHostEnvironment(host); + if (mSecurityFooter != null) { + mSecurityFooter.setHostEnvironment(host); + } mCustomizePanel = customizer; if (mCustomizePanel != null) { mCustomizePanel.setHost(mHost); @@ -341,18 +399,18 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne * @param pageIndicator indicator to use for page scrolling */ public void setFooterPageIndicator(PageIndicator pageIndicator) { - if (mTileLayout instanceof PagedTileLayout) { + if (mRegularTileLayout instanceof PagedTileLayout) { mFooterPageIndicator = pageIndicator; updatePageIndicator(); } } private void updatePageIndicator() { - if (mTileLayout instanceof PagedTileLayout) { + if (mRegularTileLayout instanceof PagedTileLayout) { if (mFooterPageIndicator != null) { mFooterPageIndicator.setVisibility(View.GONE); - ((PagedTileLayout) mTileLayout).setPageIndicator(mFooterPageIndicator); + ((PagedTileLayout) mRegularTileLayout).setPageIndicator(mFooterPageIndicator); } } } @@ -364,6 +422,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne public void updateResources() { int tileSize = getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size); int tileBg = getResources().getDimensionPixelSize(R.dimen.qs_tile_background_size); + mFooterMarginStartHorizontal = getResources().getDimensionPixelSize( + R.dimen.qs_footer_horizontal_margin); mVisualTilePadding = (int) ((tileSize - tileBg) / 2.0f); updatePadding(); @@ -379,8 +439,15 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne protected void updatePadding() { final Resources res = mContext.getResources(); + int padding = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top); + if (mUsingHorizontalLayout) { + // When using the horizontal layout, our space is quite constrained. We therefore + // reduce some of the padding on the top, which makes the brightness bar overlapp, + // but since that has naturally quite a bit of built in padding, that's fine. + padding = (int) (padding * 0.6f); + } setPaddingRelative(getPaddingStart(), - res.getDimensionPixelSize(R.dimen.qs_panel_padding_top), + padding, getPaddingEnd(), res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom)); } @@ -388,10 +455,165 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - mFooter.onConfigurationChanged(); + if (mSecurityFooter != null) { + mSecurityFooter.onConfigurationChanged(); + } updateResources(); updateBrightnessMirror(); + + if (newConfig.orientation != mLastOrientation) { + mLastOrientation = newConfig.orientation; + switchTileLayout(); + } + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mFooter = findViewById(R.id.qs_footer); + switchTileLayout(true /* force */); + } + + boolean switchTileLayout() { + return switchTileLayout(false /* force */); + } + + private boolean switchTileLayout(boolean force) { + /** Whether or not the QuickQSPanel currently contains a media player. */ + boolean horizontal = shouldUseHorizontalLayout(); + if (horizontal != mUsingHorizontalLayout || force) { + mUsingHorizontalLayout = horizontal; + View visibleView = horizontal ? mHorizontalLinearLayout : (View) mRegularTileLayout; + View hiddenView = horizontal ? (View) mRegularTileLayout : mHorizontalLinearLayout; + ViewGroup newParent = horizontal ? mHorizontalContentContainer : this; + QSTileLayout newLayout = horizontal ? mHorizontalTileLayout : mRegularTileLayout; + if (hiddenView != null && + (mRegularTileLayout != mHorizontalTileLayout || + hiddenView != mRegularTileLayout)) { + // Only hide the view if the horizontal and the regular view are different, + // otherwise its reattached. + hiddenView.setVisibility(View.GONE); + } + visibleView.setVisibility(View.VISIBLE); + switchAllContentToParent(newParent, newLayout); + reAttachMediaHost(); + if (mTileLayout != null) { + mTileLayout.setListening(false); + for (TileRecord record : mRecords) { + mTileLayout.removeTile(record); + record.tile.removeCallback(record.callback); + } + } + mTileLayout = newLayout; + if (mHost != null) setTiles(mHost.getTiles()); + newLayout.setListening(mListening); + if (needsDynamicRowsAndColumns()) { + newLayout.setMinRows(horizontal ? 2 : 1); + // Let's use 3 columns to match the current layout + newLayout.setMaxColumns(horizontal ? 3 : TileLayout.NO_MAX_COLUMNS); + } + updateTileLayoutMargins(); + updateFooterMargin(); + updateMediaHostContentMargins(); + updateHorizontalLinearLayoutMargins(); + updatePadding(); + return true; + } + return false; + } + + private void updateHorizontalLinearLayoutMargins() { + if (mHorizontalLinearLayout != null && !displayMediaMarginsOnMedia()) { + LayoutParams lp = (LayoutParams) mHorizontalLinearLayout.getLayoutParams(); + lp.bottomMargin = mMediaTotalBottomMargin - getPaddingBottom(); + mHorizontalLinearLayout.setLayoutParams(lp); + } + } + + /** + * @return true if the margin bottom of the media view should be on the media host or false + * if they should be on the HorizontalLinearLayout. Returning {@code false} is useful + * to visually center the tiles in the Media view, which doesn't work when the + * expanded panel actually scrolls. + */ + protected boolean displayMediaMarginsOnMedia() { + return true; + } + + protected boolean needsDynamicRowsAndColumns() { + return true; + } + + private void switchAllContentToParent(ViewGroup parent, QSTileLayout newLayout) { + int index = parent == this ? mMovableContentStartIndex : 0; + + // Let's first move the tileLayout to the new parent, since that should come first. + switchToParent((View) newLayout, parent, index); + index++; + + if (mSecurityFooter != null) { + View view = mSecurityFooter.getView(); + LinearLayout.LayoutParams layoutParams = (LayoutParams) view.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(view, mHeaderContainer, 1 /* always in second place */); + } else { + layoutParams.width = LayoutParams.WRAP_CONTENT; + layoutParams.weight = 0; + switchToParent(view, parent, index); + index++; + } + view.setLayoutParams(layoutParams); + } + + if (mFooter != null) { + // Then the footer with the settings + switchToParent(mFooter, parent, index); + } + } + + private void switchToParent(View child, ViewGroup parent, int index) { + ViewGroup currentParent = (ViewGroup) child.getParent(); + if (currentParent != parent || currentParent.indexOfChild(child) != index) { + if (currentParent != null) { + currentParent.removeView(child); + } + parent.addView(child, index); + } + } + + private boolean shouldUseHorizontalLayout() { + return mUsingMediaPlayer && mMediaHost.getVisible() + && getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE; + } + + protected void reAttachMediaHost() { + if (!mUsingMediaPlayer) { + return; + } + boolean horizontal = shouldUseHorizontalLayout(); + ViewGroup host = mMediaHost.getHostView(); + ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this; + ViewGroup currentParent = (ViewGroup) host.getParent(); + if (currentParent != newParent) { + if (currentParent != null) { + currentParent.removeView(host); + } + newParent.addView(host); + LinearLayout.LayoutParams layoutParams = (LayoutParams) host.getLayoutParams(); + layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; + layoutParams.width = horizontal ? 0 : ViewGroup.LayoutParams.MATCH_PARENT; + layoutParams.weight = horizontal ? 1.2f : 0; + // Add any bottom margin, such that the total spacing is correct. This is only + // necessary if the view isn't horizontal, since otherwise the padding is + // carried in the parent of this view (to ensure correct vertical alignment) + layoutParams.bottomMargin = !horizontal || displayMediaMarginsOnMedia() + ? mMediaTotalBottomMargin - getPaddingBottom() : 0; + } } public void updateBrightnessMirror() { @@ -457,13 +679,18 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne public void setListening(boolean listening, boolean expanded) { setListening(listening && expanded); - getFooter().setListening(listening); + if (mSecurityFooter != null) { + mSecurityFooter.setListening(listening); + } // Set the listening as soon as the QS fragment starts listening regardless of the expansion, // so it will update the current brightness before the slider is visible. setBrightnessListening(listening); } public void setBrightnessListening(boolean listening) { + if (mBrightnessController == null) { + return; + } if (listening) { mBrightnessController.registerCallbacks(); } else { @@ -472,11 +699,15 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } public void refreshAllTiles() { - mBrightnessController.checkRestrictionAndSetEnabled(); + if (mBrightnessController != null) { + mBrightnessController.checkRestrictionAndSetEnabled(); + } for (TileRecord r : mRecords) { r.tile.refreshState(); } - mFooter.refreshState(); + if (mSecurityFooter != null) { + mSecurityFooter.refreshState(); + } } public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) { @@ -728,12 +959,15 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne return null; } - public QSSecurityFooter getFooter() { - return mFooter; + @Nullable + public QSSecurityFooter getSecurityFooter() { + return mSecurityFooter; } public void showDeviceMonitoringDialog() { - mFooter.showDeviceMonitoringDialog(); + if (mSecurityFooter != null) { + mSecurityFooter.showDeviceMonitoringDialog(); + } } public void setContentMargins(int startMargin, int endMargin) { @@ -744,6 +978,24 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne updateTileLayoutMargins(mContentMarginStart - mVisualTilePadding, mContentMarginEnd - mVisualTilePadding); updateMediaHostContentMargins(); + updateFooterMargin(); + } + + private void updateFooterMargin() { + if (mFooter != null) { + int footerMargin = 0; + int indicatorMargin = 0; + if (mUsingHorizontalLayout) { + footerMargin = mFooterMarginStartHorizontal; + indicatorMargin = footerMargin - mVisualMarginEnd; + } + updateMargins(mFooter, footerMargin, 0); + // The page indicator isn't centered anymore because of the visual positioning. + // Let's fix it by adding some margin + if (mFooterPageIndicator != null) { + updateMargins(mFooterPageIndicator, 0, indicatorMargin); + } + } } /** @@ -754,16 +1006,30 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne * @param visualMarginEnd the visual end margin of the tile, adjusted for local insets * to the tile. This can be set on a tileLayout */ - protected void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) { - updateMargins((View) mTileLayout, visualMarginStart, visualMarginEnd); + private void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) { + mVisualMarginStart = visualMarginStart; + mVisualMarginEnd = visualMarginEnd; + updateTileLayoutMargins(); + } + + private void updateTileLayoutMargins() { + int marginEnd = mVisualMarginEnd; + if (mUsingHorizontalLayout) { + marginEnd = 0; + } + updateMargins((View) mTileLayout, mVisualMarginStart, marginEnd); } /** * Update the margins of the media hosts */ protected void updateMediaHostContentMargins() { - if (mUsingMediaPlayer && mMediaHost != null) { - updateMargins(mMediaHost.getHostView(), mContentMarginStart, mContentMarginEnd); + if (mUsingMediaPlayer) { + int marginStart = mContentMarginStart; + if (mUsingHorizontalLayout) { + marginStart = 0; + } + updateMargins(mMediaHost.getHostView(), marginStart, mContentMarginEnd); } } @@ -785,6 +1051,13 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne return mMediaHost; } + /** + * Set the header container of quick settings. + */ + public void setHeaderContainer(@NonNull ViewGroup headerContainer) { + mHeaderContainer = headerContainer; + } + private class H extends Handler { private static final int SHOW_DETAIL = 1; private static final int SET_TILE_VISIBILITY = 2; @@ -812,6 +1085,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } } + protected static class Record { DetailAdapter detailAdapter; int x; @@ -841,6 +1115,26 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne void setListening(boolean listening); + /** + * Set the minimum number of rows to show + * + * @param minRows the minimum. + */ + default boolean setMinRows(int minRows) { + return false; + } + + /** + * Set the max number of collums to show + * + * @param maxColumns the maximum + * + * @return true if the number of visible columns has changed. + */ + default boolean setMaxColumns(int maxColumns) { + return false; + } + default void setExpansion(float expansion) {} int getNumVisibleTiles(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java index 476af20b78f4..7bcaa7263cc4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java @@ -51,6 +51,7 @@ import com.android.systemui.statusbar.policy.SecurityController; public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener { protected static final String TAG = "QSSecurityFooter"; protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DEBUG_FORCE_VISIBLE = false; private final View mRootView; private final TextView mFooterText; @@ -60,7 +61,6 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic private final SecurityController mSecurityController; private final ActivityStarter mActivityStarter; private final Handler mMainHandler; - private final View mDivider; private final UserManager mUm; @@ -85,7 +85,6 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic mActivityStarter = Dependency.get(ActivityStarter.class); mSecurityController = Dependency.get(SecurityController.class); mHandler = new H(Dependency.get(Dependency.BG_LOOPER)); - mDivider = qsPanel == null ? null : qsPanel.getDivider(); mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); } @@ -177,7 +176,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled, String vpnName, String vpnNameWorkProfile, CharSequence organizationName, CharSequence workProfileName) { - if (isDeviceManaged) { + if (isDeviceManaged || DEBUG_FORCE_VISIBLE) { if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) { if (organizationName == null) { return mContext.getString( @@ -451,8 +450,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic if (mFooterTextContent != null) { mFooterText.setText(mFooterTextContent); } - mRootView.setVisibility(mIsVisible ? View.VISIBLE : View.GONE); - if (mDivider != null) mDivider.setVisibility(mIsVisible ? View.GONE : View.VISIBLE); + mRootView.setVisibility(mIsVisible || DEBUG_FORCE_VISIBLE ? View.VISIBLE : View.GONE); } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index 94b4cee92965..affb7b91b6a5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -24,7 +24,6 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; -import android.view.ViewGroup; import android.widget.LinearLayout; import com.android.internal.logging.UiEventLogger; @@ -61,15 +60,7 @@ public class QuickQSPanel extends QSPanel { private boolean mDisabledByPolicy; private int mMaxTiles; protected QSPanel mFullPanel; - /** Whether or not the QuickQSPanel currently contains a media player. */ - private boolean mShowHorizontalTileLayout; - private LinearLayout mHorizontalLinearLayout; - // Only used with media - private QSTileLayout mHorizontalTileLayout; - private QSTileLayout mRegularTileLayout; - private int mLastOrientation = -1; - private int mMediaBottomMargin; @Inject public QuickQSPanel( @@ -82,59 +73,8 @@ public class QuickQSPanel extends QSPanel { UiEventLogger uiEventLogger ) { super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger); - if (mFooter != null) { - removeView(mFooter.getView()); - } - if (mTileLayout != null) { - for (int i = 0; i < mRecords.size(); i++) { - mTileLayout.removeTile(mRecords.get(i)); - } - removeView((View) mTileLayout); - } - mMediaBottomMargin = getResources().getDimensionPixelSize( - R.dimen.quick_settings_media_extra_bottom_margin); - if (mUsingMediaPlayer) { - mHorizontalLinearLayout = new LinearLayout(mContext); - mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL); - mHorizontalLinearLayout.setClipChildren(false); - mHorizontalLinearLayout.setClipToPadding(false); - - DoubleLineTileLayout horizontalTileLayout = new DoubleLineTileLayout(context, - mUiEventLogger); - horizontalTileLayout.setPaddingRelative( - horizontalTileLayout.getPaddingStart(), - horizontalTileLayout.getPaddingTop(), - horizontalTileLayout.getPaddingEnd(), - mContext.getResources().getDimensionPixelSize( - R.dimen.qqs_horizonal_tile_padding_bottom)); - mHorizontalTileLayout = horizontalTileLayout; - mRegularTileLayout = new HeaderTileLayout(context, mUiEventLogger); - LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1); - int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing); - lp.setMarginStart(0); - lp.setMarginEnd(marginSize); - lp.gravity = Gravity.CENTER_VERTICAL; - mHorizontalLinearLayout.addView((View) mHorizontalTileLayout, lp); - - sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); - - boolean useHorizontal = shouldUseHorizontalTileLayout(); - mTileLayout = useHorizontal ? mHorizontalTileLayout : mRegularTileLayout; - mTileLayout.setListening(mListening); - addView(mHorizontalLinearLayout, 0 /* Between brightness and footer */); - ((View) mRegularTileLayout).setVisibility(!useHorizontal ? View.VISIBLE : View.GONE); - mHorizontalLinearLayout.setVisibility(useHorizontal ? View.VISIBLE : View.GONE); - addView((View) mRegularTileLayout, 0); - super.setPadding(0, 0, 0, 0); - applyBottomMargin((View) mRegularTileLayout); - } else { - sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); - mTileLayout = new HeaderTileLayout(context, mUiEventLogger); - mTileLayout.setListening(mListening); - addView((View) mTileLayout, 0 /* Between brightness and footer */); - super.setPadding(0, 0, 0, 0); - applyBottomMargin((View) mTileLayout); - } + sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); + applyBottomMargin((View) mRegularTileLayout); } private void applyBottomMargin(View view) { @@ -144,57 +84,47 @@ public class QuickQSPanel extends QSPanel { view.setLayoutParams(layoutParams); } - private void reAttachMediaHost() { - if (mMediaHost == null) { - return; - } - boolean horizontal = shouldUseHorizontalTileLayout(); - ViewGroup host = mMediaHost.getHostView(); - ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this; - ViewGroup currentParent = (ViewGroup) host.getParent(); - if (currentParent != newParent) { - if (currentParent != null) { - currentParent.removeView(host); - } - newParent.addView(host); - LinearLayout.LayoutParams layoutParams = (LayoutParams) host.getLayoutParams(); - layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; - layoutParams.width = horizontal ? 0 : ViewGroup.LayoutParams.MATCH_PARENT; - layoutParams.weight = horizontal ? 1.5f : 0; - layoutParams.bottomMargin = mMediaBottomMargin; - } + @Override + protected void addSecurityFooter() { + // No footer needed + } + + @Override + protected void addViewsAboveTiles() { + // Nothing to add above the tiles + } + + @Override + protected TileLayout createRegularTileLayout() { + return new QuickQSPanel.HeaderTileLayout(mContext, mUiEventLogger); } @Override - protected void addMediaHostView() { - mMediaHost.setVisibleChangedListener((visible) -> { - switchTileLayout(); - return null; - }); + protected QSTileLayout createHorizontalTileLayout() { + return new DoubleLineTileLayout(mContext, mUiEventLogger); + } + + @Override + protected void initMediaHostState() { mMediaHost.setExpansion(0.0f); mMediaHost.setShowsOnlyActiveMedia(true); mMediaHost.init(MediaHierarchyManager.LOCATION_QQS); - reAttachMediaHost(); - updateMediaHostContentMargins(); } @Override - protected void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) { - if (mUsingMediaPlayer) { - updateMargins((View) mRegularTileLayout, visualMarginStart, visualMarginEnd); - updateMargins((View) mHorizontalTileLayout, visualMarginStart, 0); - } else { - updateMargins((View) mTileLayout, visualMarginStart, visualMarginEnd); - } + protected boolean needsDynamicRowsAndColumns() { + return false; // QQS always have the same layout } @Override - protected void updatePadding() { - // QS Panel is setting a top padding by default, which we don't need. + protected boolean displayMediaMarginsOnMedia() { + // Margins should be on the container to visually center the view + return false; } @Override - protected void addDivider() { + protected void updatePadding() { + // QS Panel is setting a top padding by default, which we don't need. } @Override @@ -237,60 +167,6 @@ public class QuickQSPanel extends QSPanel { } @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - if (newConfig.orientation != mLastOrientation) { - mLastOrientation = newConfig.orientation; - switchTileLayout(); - } - } - - boolean switchTileLayout() { - if (!mUsingMediaPlayer) return false; - mShowHorizontalTileLayout = shouldUseHorizontalTileLayout(); - if (mShowHorizontalTileLayout && mHorizontalLinearLayout.getVisibility() == View.GONE) { - mHorizontalLinearLayout.setVisibility(View.VISIBLE); - ((View) mRegularTileLayout).setVisibility(View.GONE); - mTileLayout.setListening(false); - for (TileRecord record : mRecords) { - mTileLayout.removeTile(record); - record.tile.removeCallback(record.callback); - } - mTileLayout = mHorizontalTileLayout; - if (mHost != null) setTiles(mHost.getTiles()); - mTileLayout.setListening(mListening); - reAttachMediaHost(); - return true; - } else if (!mShowHorizontalTileLayout - && mHorizontalLinearLayout.getVisibility() == View.VISIBLE) { - mHorizontalLinearLayout.setVisibility(View.GONE); - ((View) mRegularTileLayout).setVisibility(View.VISIBLE); - mTileLayout.setListening(false); - for (TileRecord record : mRecords) { - mTileLayout.removeTile(record); - record.tile.removeCallback(record.callback); - } - mTileLayout = mRegularTileLayout; - if (mHost != null) setTiles(mHost.getTiles()); - mTileLayout.setListening(mListening); - reAttachMediaHost(); - return true; - } - return false; - } - - private boolean shouldUseHorizontalTileLayout() { - return mMediaHost.getVisible() - && getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE; - } - - /** Returns true if this panel currently uses a horizontal tile layout. */ - public boolean usesHorizontalLayout() { - return mShowHorizontalTileLayout; - } - - @Override public void setHost(QSTileHost host, QSCustomizer customizer) { super.setHost(host, customizer); setTiles(mHost.getTiles()); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 20e47b2f2fa9..311eda2f4ad8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -36,13 +36,13 @@ import android.service.notification.ZenModeConfig; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; +import android.util.MathUtils; import android.util.Pair; import android.view.ContextThemeWrapper; import android.view.DisplayCutout; import android.view.View; import android.view.WindowInsets; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; @@ -55,6 +55,7 @@ import androidx.lifecycle.LifecycleRegistry; import com.android.settingslib.Utils; import com.android.systemui.BatteryMeterView; import com.android.systemui.DualToneHandler; +import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.DarkIconDispatcher; @@ -149,6 +150,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements private int mWaterfallTopInset; private int mCutOutPaddingLeft; private int mCutOutPaddingRight; + private float mExpandedHeaderAlpha = 1.0f; + private float mKeyguardExpansionFraction; @Inject public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, @@ -356,7 +359,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements private void updateHeaderTextContainerAlphaAnimator() { mHeaderTextContainerAlphaAnimator = new TouchAnimator.Builder() - .addFloat(mHeaderTextContainerView, "alpha", 0, 0, 1) + .addFloat(mHeaderTextContainerView, "alpha", 0, 0, mExpandedHeaderAlpha) .build(); } @@ -403,6 +406,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements updateResources(); } } + mKeyguardExpansionFraction = keyguardExpansionFraction; } public void disable(int state1, int state2, boolean animate) { @@ -596,4 +600,22 @@ public class QuickStatusBarHeader extends RelativeLayout implements } updateClockPadding(); } + + 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, + scrollY); + newAlpha = Interpolators.ALPHA_OUT.getInterpolation(newAlpha); + } + mHeaderTextContainerView.setScrollY(scrollY); + if (newAlpha != mExpandedHeaderAlpha) { + mExpandedHeaderAlpha = newAlpha; + mHeaderTextContainerView.setAlpha(MathUtils.lerp(0.0f, mExpandedHeaderAlpha, + mKeyguardExpansionFraction)); + updateHeaderTextContainerAlphaAnimator(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 383c29d90a22..694492a33524 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -17,6 +17,7 @@ import java.util.ArrayList; public class TileLayout extends ViewGroup implements QSTileLayout { + public static final int NO_MAX_COLUMNS = 100; private static final float TILE_ASPECT = 1.2f; private static final String TAG = "TileLayout"; @@ -36,6 +37,9 @@ public class TileLayout extends ViewGroup implements QSTileLayout { // Prototyping with less rows private final boolean mLessRows; + private int mMinRows = 1; + private int mMaxColumns = NO_MAX_COLUMNS; + private int mResourceColumns; public TileLayout(Context context) { this(context, null); @@ -64,6 +68,22 @@ public class TileLayout extends ViewGroup implements QSTileLayout { } } + @Override + public boolean setMinRows(int minRows) { + if (mMinRows != minRows) { + mMinRows = minRows; + updateResources(); + return true; + } + return false; + } + + @Override + public boolean setMaxColumns(int maxColumns) { + mMaxColumns = maxColumns; + return updateColumns(); + } + public void addTile(TileRecord tile) { mRecords.add(tile); tile.tile.setListening(this, mListening); @@ -91,21 +111,26 @@ public class TileLayout extends ViewGroup implements QSTileLayout { public boolean updateResources() { final Resources res = mContext.getResources(); - final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); + mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); mCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height); mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal); mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical); mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top); mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows)); - if (mLessRows) mMaxAllowedRows = Math.max(1, mMaxAllowedRows - 1); - if (mColumns != columns) { - mColumns = columns; + if (mLessRows) mMaxAllowedRows = Math.max(mMinRows, mMaxAllowedRows - 1); + if (updateColumns()) { requestLayout(); return true; } return false; } + private boolean updateColumns() { + int oldColumns = mColumns; + mColumns = Math.min(mResourceColumns, mMaxColumns); + return oldColumns != mColumns; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // If called with AT_MOST, it will limit the number of rows. If called with UNSPECIFIED @@ -142,18 +167,19 @@ public class TileLayout extends ViewGroup implements QSTileLayout { * Determines the maximum number of rows that can be shown based on height. Clips at a minimum * of 1 and a maximum of mMaxAllowedRows. * - * @param heightMeasureSpec Available height. + * @param allowedHeight The height this view has visually available * @param tilesCount Upper limit on the number of tiles to show. to prevent empty rows. */ - public boolean updateMaxRows(int heightMeasureSpec, int tilesCount) { - final int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mCellMarginTop + public boolean updateMaxRows(int allowedHeight, int tilesCount) { + final int availableHeight = allowedHeight - mCellMarginTop + // Add the cell margin in order to divide easily by the height + the margin below + mCellMarginVertical; final int previousRows = mRows; mRows = availableHeight / (mCellHeight + mCellMarginVertical); - if (mRows >= mMaxAllowedRows) { + if (mRows < mMinRows) { + mRows = mMinRows; + } else if (mRows >= mMaxAllowedRows) { mRows = mMaxAllowedRows; - } else if (mRows <= 1) { - mRows = 1; } if (mRows > (tilesCount + mColumns - 1) / mColumns) { mRows = (tilesCount + mColumns - 1) / mColumns; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 8889510cde28..6622b9485b67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -104,7 +104,6 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.stack.AnimationProperties; -import com.android.systemui.statusbar.notification.stack.MediaHeaderView; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; @@ -3069,7 +3068,7 @@ public class NotificationPanelViewController extends PanelViewController { return new TouchHandler() { @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) { + if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) { return false; } initDownStates(event); @@ -3096,7 +3095,8 @@ public class NotificationPanelViewController extends PanelViewController { @Override public boolean onTouch(View v, MotionEvent event) { - if (mBlockTouches || (mQs != null && mQs.isCustomizing())) { + if (mBlockTouches || (mQsFullyExpanded && mQs != null + && mQs.disallowPanelTouches())) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt new file mode 100644 index 000000000000..bab93475c8bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import android.content.Context +import android.util.AttributeSet +import android.widget.LinearLayout + +/** + * Basically a normal linear layout but doesn't grow its children with weight 1 even when its + * measured with exactly. + */ +class NeverExactlyLinearLayout @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr) { + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + + val (widthExactly, usedWidthSpec, width) = getNonExactlyMeasureSpec(widthMeasureSpec) + val (heightExactly, usedHeightSpec, height) = getNonExactlyMeasureSpec(heightMeasureSpec) + + super.onMeasure(usedWidthSpec, usedHeightSpec) + if (widthExactly || heightExactly) { + val newWidth = if (widthExactly) width else measuredWidth + val newHeight = if (heightExactly) height else measuredHeight + setMeasuredDimension(newWidth, newHeight) + } + } + + /** + * Obtain a measurespec that's not exactly + * + * @return a triple, where we return 1. if this was exactly, 2. the new measurespec, 3. the size + * of the measurespec + */ + private fun getNonExactlyMeasureSpec(measureSpec: Int): Triple<Boolean, Int, Int> { + var newSpec = measureSpec + val isExactly = MeasureSpec.getMode(measureSpec) == MeasureSpec.EXACTLY + val size = MeasureSpec.getSize(measureSpec) + if (isExactly) { + newSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST) + } + return Triple(isExactly, newSpec, size) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java index 05b31c86559b..cbb0711f78f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java @@ -51,6 +51,7 @@ import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.policy.SecurityController; +import com.android.systemui.util.animation.UniqueObjectHostView; import org.junit.Before; import org.junit.Test; @@ -108,12 +109,14 @@ public class QSPanelTest extends SysuiTestCase { mDependency.injectMockDependency(SecurityController.class); mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); mContext.addMockSystemService(Context.USER_SERVICE, mock(UserManager.class)); + when(mMediaHost.getHostView()).thenReturn(new UniqueObjectHostView(getContext())); mUiEventLogger = new UiEventLoggerFake(); mTestableLooper.runWithLooper(() -> { mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class); mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher, mQSLogger, mMediaHost, mUiEventLogger); + mQsPanel.onFinishInflate(); // Provides a parent with non-zero size for QSPanel mParentView = new FrameLayout(mContext); mParentView.addView(mQsPanel); |