From 516b6dfcc49f6309432b74bc4767beb9171886d5 Mon Sep 17 00:00:00 2001 From: Selim Cinek Date: Wed, 3 Jun 2020 17:22:18 -0700 Subject: [PATCH 1/2] Fixed an issue where the media player would shift when editing QS Because we were pinning it to the bottom, the player would shift and be clipped off at times. Bug: 154332040 Test: add media player, click on edit, observe no flicker Change-Id: I56ea3eeecad22a588b7066f32223631127024bfe --- .../com/android/systemui/qs/QSFragment.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 6af9e1ef45711..dd02edd506b86 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -427,13 +427,15 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca if (Utils.useQsMediaPlayer(getContext())) { mContainer.getLocationOnScreen(mTmpLocation); float absoluteBottomPosition = mTmpLocation[1] + mContainer.getHeight(); - pinToBottom(absoluteBottomPosition, mQSPanel.getMediaHost()); + // The expanded media host should never move below the laid out position + pinToBottom(absoluteBottomPosition, mQSPanel.getMediaHost(), true /* expanded */); + // The expanded media host should never move above the laid out position pinToBottom(absoluteBottomPosition - mHeader.getPaddingBottom(), - mHeader.getHeaderQsPanel().getMediaHost()); + mHeader.getHeaderQsPanel().getMediaHost(), false /* expanded */); } } - private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost) { + private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded) { View hostView = mediaHost.getHostView(); if (mLastQSExpansion > 0) { ViewGroup.MarginLayoutParams params = @@ -442,7 +444,15 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca - hostView.getHeight(); float currentPosition = mediaHost.getCurrentBounds().top - hostView.getTranslationY(); - hostView.setTranslationY(targetPosition - currentPosition); + float translationY = targetPosition - currentPosition; + if (expanded) { + // Never go below the laid out position. This is necessary since the qs panel can + // change in height and we don't want to ever go below it's position + translationY = Math.min(translationY, 0); + } else { + translationY = Math.max(translationY, 0); + } + hostView.setTranslationY(translationY); } else { hostView.setTranslationY(0); } From 2a1cab119283ea9edaa2384b05780b11a51b27f7 Mon Sep 17 00:00:00 2001 From: Selim Cinek Date: Mon, 1 Jun 2020 19:19:58 -0700 Subject: [PATCH 2/2] Changed the landscape experience of quick settings When introducing Media into quick settings, the landscape experience never really worked well. The music player would be cut off on the bottom and users could never reach their playback controls. Instead of the vertical layout, we're now rendering quick settings side by side with the player. Using some densities, this could still be too large, so we're also introducing a scrollview for the expanded quick settings. This also makes sure all the paddings and spacings are up to spec. This also fixes behavior where a user could still collapse the panel while the user switcher was showing. Bug: 154332040 Test: atest SystemUITests Change-Id: I3b8babc2cc6a91e19824b67f043aa5044dc370b9 --- .../com/android/systemui/plugins/qs/QS.java | 15 +- packages/SystemUI/res/layout/qs_detail.xml | 5 +- packages/SystemUI/res/layout/qs_divider.xml | 20 - .../SystemUI/res/layout/qs_footer_impl.xml | 3 + .../SystemUI/res/layout/qs_paged_page.xml | 2 - packages/SystemUI/res/layout/qs_panel.xml | 22 +- .../res/layout/quick_settings_footer.xml | 17 +- .../res/layout/quick_settings_header_info.xml | 5 +- .../quick_status_bar_expanded_header.xml | 2 +- packages/SystemUI/res/values-land/dimens.xml | 1 - packages/SystemUI/res/values-night/styles.xml | 5 - .../res/values-sw600dp-land/config.xml | 3 - .../res/values-sw600dp-port/dimens.xml | 21 + .../SystemUI/res/values-sw600dp/dimens.xml | 2 - .../res/values-sw900dp-land/dimen.xml | 23 - .../res/values-w550dp-land/config.xml | 6 +- .../res/values-w550dp-land/dimens.xml | 22 - .../res/values-w650dp-land/dimens.xml | 21 + packages/SystemUI/res/values/config.xml | 3 - packages/SystemUI/res/values/dimens.xml | 7 +- packages/SystemUI/res/values/styles.xml | 5 - .../systemui/media/MediaHierarchyManager.kt | 10 + .../systemui/qs/DoubleLineTileLayout.kt | 14 +- .../qs/NonInterceptingScrollView.java | 51 +- .../android/systemui/qs/PagedTileLayout.java | 67 ++- .../com/android/systemui/qs/QSAnimator.java | 34 +- .../android/systemui/qs/QSContainerImpl.java | 23 +- .../com/android/systemui/qs/QSFragment.java | 86 ++-- .../src/com/android/systemui/qs/QSPanel.java | 440 +++++++++++++++--- .../android/systemui/qs/QSSecurityFooter.java | 8 +- .../com/android/systemui/qs/QuickQSPanel.java | 182 ++------ .../systemui/qs/QuickStatusBarHeader.java | 26 +- .../com/android/systemui/qs/TileLayout.java | 46 +- .../NotificationPanelViewController.java | 6 +- .../systemui/util/NeverExactlyLinearLayout.kt | 60 +++ .../com/android/systemui/qs/QSPanelTest.java | 3 + 36 files changed, 826 insertions(+), 440 deletions(-) delete mode 100644 packages/SystemUI/res/layout/qs_divider.xml create mode 100644 packages/SystemUI/res/values-sw600dp-port/dimens.xml delete mode 100644 packages/SystemUI/res/values-sw900dp-land/dimen.xml delete mode 100644 packages/SystemUI/res/values-w550dp-land/dimens.xml create mode 100644 packages/SystemUI/res/values-w650dp-land/dimens.xml create mode 100644 packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt 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 85a9fec859f3b..fffcafbf88fbc 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 294bd50fcf8b4..59e1a755d7d2c 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" /> - - + diff --git a/packages/SystemUI/res/layout/qs_divider.xml b/packages/SystemUI/res/layout/qs_divider.xml deleted file mode 100644 index 39d48ea4746ef..0000000000000 --- a/packages/SystemUI/res/layout/qs_divider.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index ebfd0a0fd5373..5c00af5705e99 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"/> diff --git a/packages/SystemUI/res/layout/qs_paged_page.xml b/packages/SystemUI/res/layout/qs_paged_page.xml index a8960d9b94371..5c8b2b08324f0 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 cdf84260e3998..761ab03ee87e0 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" /> - - - - + android:layout_weight="1"> + + + + diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml index 15f398aa52e6e..e7c7b5fbf8908 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. --> - - + android:textColor="?android:attr/textColorPrimary"/> + android:tint="?android:attr/textColorPrimary" /> - + diff --git a/packages/SystemUI/res/layout/quick_settings_header_info.xml b/packages/SystemUI/res/layout/quick_settings_header_info.xml index e6ef9b4b902cf..fb82304663aa6 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"> 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 abeb33111c40a..dc34127496f65 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" /> 9dp 4 - true 0dp 136dp diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml index 50261e1b21393..4fdeb6fa4a92c 100644 --- a/packages/SystemUI/res/values-night/styles.xml +++ b/packages/SystemUI/res/values-night/styles.xml @@ -29,9 +29,4 @@ ?android:attr/textColorPrimary - - diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index c4c4671522814..3b00ad1bf0c4a 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 --> - - false - 3 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 0000000000000..40838f362f5cc --- /dev/null +++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml @@ -0,0 +1,21 @@ + + + + + 416dp + + diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 8f73d231c7329..fdf4e3b1b7961 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -16,8 +16,6 @@ */ --> - - 416dp 430dip 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 1e0600ed5fe0c..0000000000000 --- a/packages/SystemUI/res/values-sw900dp-land/dimen.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - 544dp - - - diff --git a/packages/SystemUI/res/values-w550dp-land/config.xml b/packages/SystemUI/res/values-w550dp-land/config.xml index 16d5317636a23..a33f1312521fe 100644 --- a/packages/SystemUI/res/values-w550dp-land/config.xml +++ b/packages/SystemUI/res/values-w550dp-land/config.xml @@ -20,9 +20,5 @@ - - - true - - 4 + 6 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 017ca69878207..0000000000000 --- a/packages/SystemUI/res/values-w550dp-land/dimens.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - 544dp - - 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 0000000000000..108d6cf16fec8 --- /dev/null +++ b/packages/SystemUI/res/values-w650dp-land/dimens.xml @@ -0,0 +1,21 @@ + + + + + 644dp + + diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 00537ff0466dc..848cdb1e831c6 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -95,9 +95,6 @@ 6 - - false - 3 diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 73d8e9a0d8a72..ab0f876a68b89 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1075,8 +1075,7 @@ 8dp - 16dp - 6dp + 16dp 0dp 0dp 24dp @@ -1268,8 +1267,8 @@ 16dp 52dp 20dp - 8dp - 8dp + 16dp + 22dp 5dp 5dp diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 39f78bf460281..ed36bdbe1e7e7 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -387,11 +387,6 @@ @drawable/ic_arrow_back - - diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 775a1649702ae..b86e1d0503d45 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 40d317c7bb223..dc157a8dd2575 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 c204d94916a47..aa17c4aa79b1d 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 aaff9ac47ebf4..28369925367ae 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"); @@ -341,18 +351,55 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { setClipBounds(mClippingRect); } + @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 ce002297e1a1f..bc8f5a8fb6526 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 1c3b6850afc12..0332bc3e0618e 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 dd02edd506b86..f1bb8996e1810 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); @@ -318,11 +330,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca return mQSPanel.isShowingCustomize() || mQSDetail.isShowingDetail(); } - @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 78448785fe2f6..ecdb2c91ca48d 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 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); + 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); + 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); - - mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate( - R.layout.qs_paged_tile_layout, this, false); - 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(); - 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 void addMediaHostView() { + + protected QSTileLayout createHorizontalTileLayout() { + return createRegularTileLayout(); + } + + 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 476af20b78f49..7bcaa7263cc48 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 94b4cee92965c..affb7b91b6a5f 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,48 +84,42 @@ 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 addMediaHostView() { - mMediaHost.setVisibleChangedListener((visible) -> { - switchTileLayout(); - return null; - }); + protected void addViewsAboveTiles() { + // Nothing to add above the tiles + } + + @Override + protected TileLayout createRegularTileLayout() { + return new QuickQSPanel.HeaderTileLayout(mContext, mUiEventLogger); + } + + @Override + 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 boolean displayMediaMarginsOnMedia() { + // Margins should be on the container to visually center the view + return false; } @Override @@ -193,10 +127,6 @@ public class QuickQSPanel extends QSPanel { // QS Panel is setting a top padding by default, which we don't need. } - @Override - protected void addDivider() { - } - @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); @@ -236,60 +166,6 @@ public class QuickQSPanel extends QSPanel { super.drawTile(r, state); } - @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); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 20e47b2f2fa94..311eda2f4ad88 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 383c29d90a22c..694492a335246 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 8889510cde288..6622b9485b676 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 0000000000000..bab93475c8bf3 --- /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 { + 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 05b31c86559b1..cbb0711f78f85 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);