Add QS scrolling support
The brightness slider and tile layout are now a scrollview and
can handle nested scrolling between the views. There is some
black magic which manages the touches to allow a scroll up to
continue into a collapse gesture.
TODO: need to add edge effect colors to theme
Test: visual
Bug: 70799330
Change-Id: Ief0c1480f001cae62175c8ac4f3784daaa0ed2b8
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 a648345e..30d1352 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
@@ -14,6 +14,7 @@
package com.android.systemui.plugins.qs;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@@ -65,6 +66,18 @@
default void setHasNotifications(boolean hasNotifications) {
}
+ /**
+ * 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.
+ */
+ default boolean onInterceptTouchEvent(MotionEvent event) {
+ return isCustomizing();
+ }
+
@ProvidesInterface(version = HeightListener.VERSION)
interface HeightListener {
int VERSION = 1;
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b749a18..da9a6be 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -334,6 +334,8 @@
<dimen name="qs_footer_padding_end">24dp</dimen>
<dimen name="qs_footer_icon_size">16dp</dimen>
+ <dimen name="qs_notif_collapsed_space">64dp</dimen>
+
<!-- Desired qs icon overlay size. -->
<dimen name="qs_detail_icon_overlay_size">24dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 8f18800..95185c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -244,10 +244,8 @@
mFirstPageDelayedAnimator = new TouchAnimator.Builder()
.setStartDelay(EXPANDED_TILE_DELAY)
.addFloat(tileLayout, "alpha", 0, 1)
- .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
.addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
.addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build();
- mAllViews.add(mQsPanel.getPageIndicator());
mAllViews.add(mQsPanel.getDivider());
mAllViews.add(mQsPanel.getFooter().getView());
float px = 0;
@@ -265,7 +263,6 @@
}
mNonfirstPageAnimator = new TouchAnimator.Builder()
.addFloat(mQuickQsPanel, "alpha", 1, 0)
- .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
.addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
.setListener(mNonFirstPageListener)
.setEndDelay(.5f)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 7320b86..6b0d592 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -17,11 +17,11 @@
package com.android.systemui.qs;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
@@ -80,11 +80,22 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ getDisplay().getRealSize(mSizePoint);
+
// Since we control our own bottom, be whatever size we want.
// Otherwise the QSPanel ends up with 0 height when the window is only the
// size of the status bar.
- mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(
- MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED));
+ Configuration config = getResources().getConfiguration();
+ boolean navBelow = config.smallestScreenWidthDp >= 600
+ || config.orientation != Configuration.ORIENTATION_LANDSCAPE;
+ MarginLayoutParams params = (MarginLayoutParams) mQSPanel.getLayoutParams();
+ int maxQs = mSizePoint.y - params.topMargin - params.bottomMargin - getPaddingBottom()
+ - getResources().getDimensionPixelSize(R.dimen.qs_notif_collapsed_space);
+ if (navBelow) {
+ maxQs -= getResources().getDimensionPixelSize(R.dimen.navigation_bar_height);
+ }
+ mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
+
int width = mQSPanel.getMeasuredWidth();
LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams();
int height = layoutParams.topMargin + layoutParams.bottomMargin
@@ -94,7 +105,6 @@
// QSCustomizer will always be the height of the screen, but do this after
// other measuring to avoid changing the height of the QS.
- getDisplay().getRealSize(mSizePoint);
mQSCustomizer.measure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(mSizePoint.y, MeasureSpec.EXACTLY));
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index cdf0c0f..29a8f16 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -25,6 +25,7 @@
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;
@@ -207,6 +208,11 @@
}
@Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ return isCustomizing() || mQSPanel.onInterceptTouchEvent(event);
+ }
+
+ @Override
public void setHeaderClickable(boolean clickable) {
if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 8f41084..f7a03ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -28,8 +28,8 @@
import android.service.quicksettings.Tile;
import android.util.AttributeSet;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
-import android.widget.ImageView;
import android.widget.LinearLayout;
import com.android.internal.logging.MetricsLogger;
@@ -62,11 +62,8 @@
protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
protected final View mBrightnessView;
private final H mHandler = new H();
- private final View mPageIndicator;
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
- private int mPanelPaddingBottom;
- private int mBrightnessPaddingTop;
protected boolean mExpanded;
protected boolean mListening;
@@ -77,6 +74,7 @@
protected QSSecurityFooter mFooter;
private boolean mGridContentVisible = true;
+ private QSScrollLayout mScrollLayout;
protected QSTileLayout mTileLayout;
private QSCustomizer mCustomizePanel;
@@ -95,18 +93,12 @@
setOrientation(VERTICAL);
- mBrightnessView = LayoutInflater.from(context).inflate(
- R.layout.quick_settings_brightness_dialog, this, false);
- addView(mBrightnessView);
-
- setupTileLayout();
-
- mPageIndicator = LayoutInflater.from(context).inflate(
- R.layout.qs_page_indicator, this, false);
- addView(mPageIndicator);
- if (mTileLayout instanceof PagedTileLayout) {
- ((PagedTileLayout) mTileLayout).setPageIndicator((PageIndicator) mPageIndicator);
- }
+ mBrightnessView = LayoutInflater.from(mContext).inflate(
+ R.layout.quick_settings_brightness_dialog, this, false);
+ mTileLayout = new TileLayout(mContext);
+ mTileLayout.setListening(mListening);
+ mScrollLayout = new QSScrollLayout(mContext, mBrightnessView, (View) mTileLayout);
+ addView(mScrollLayout);
addDivider();
@@ -131,17 +123,6 @@
return mDivider;
}
- public View getPageIndicator() {
- return mPageIndicator;
- }
-
- protected void setupTileLayout() {
- mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
- R.layout.qs_paged_tile_layout, this, false);
- mTileLayout.setListening(mListening);
- addView((View) mTileLayout);
- }
-
public boolean isShowingCustomize() {
return mCustomizePanel != null && mCustomizePanel.isCustomizing();
}
@@ -241,9 +222,13 @@
public void updateResources() {
final Resources res = mContext.getResources();
- mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
- mBrightnessPaddingTop = res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top);
- setPadding(0, mBrightnessPaddingTop, 0, mPanelPaddingBottom);
+ mBrightnessView.setPadding(
+ mBrightnessView.getPaddingLeft(),
+ res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top),
+ mBrightnessView.getPaddingRight(),
+ mBrightnessView.getPaddingBottom());
+ setPadding(
+ 0, 0, 0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom));
for (TileRecord r : mRecords) {
r.tile.clearState();
}
@@ -282,8 +267,11 @@
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
- if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
- ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
+ if (!mExpanded) {
+ if (mTileLayout instanceof PagedTileLayout) {
+ ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
+ }
+ mScrollLayout.setScrollY(0);
}
mMetricsLogger.visibility(MetricsEvent.QS_PANEL, mExpanded);
if (!mExpanded) {
@@ -564,6 +552,11 @@
mFooter.showDeviceMonitoringDialog();
}
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ return mExpanded && mScrollLayout.shouldIntercept(event);
+ }
+
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
new file mode 100644
index 0000000..9a74787
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.v4.widget.NestedScrollView;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+import android.widget.LinearLayout;
+
+/**
+ * Quick setting scroll view containing the brightness slider and the QS tiles.
+ *
+ * <p>Call {@link #shouldIntercept(MotionEvent)} from parent views'
+ * {@link #onInterceptTouchEvent(MotionEvent)} method to determine whether this view should
+ * consume the touch event.
+ */
+public class QSScrollLayout extends NestedScrollView {
+ private final int mTouchSlop;
+ private int mLastMotionY;
+ private Rect mHitRect = new Rect();
+
+ public QSScrollLayout(Context context, View... children) {
+ super(context);
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ LinearLayout linearLayout = new LinearLayout(mContext);
+ linearLayout.setLayoutParams(new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+ for (View view : children) {
+ linearLayout.addView(view);
+ }
+ addView(linearLayout);
+ }
+
+ public boolean shouldIntercept(MotionEvent ev) {
+ getHitRect(mHitRect);
+ if (!mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
+ // Do not intercept touches that are not within this view's bounds.
+ return false;
+ }
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mLastMotionY = (int) ev.getY();
+ } else if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
+ // Do not allow NotificationPanelView to intercept touch events when this
+ // view can be scrolled down.
+ if (mLastMotionY >= 0 && Math.abs(ev.getY() - mLastMotionY) > mTouchSlop
+ && canScrollVertically(1)) {
+ requestParentDisallowInterceptTouchEvent(true);
+ mLastMotionY = (int) ev.getY();
+ return true;
+ }
+ } else if (ev.getActionMasked() == MotionEvent.ACTION_CANCEL
+ || ev.getActionMasked() == MotionEvent.ACTION_UP) {
+ mLastMotionY = -1;
+ requestParentDisallowInterceptTouchEvent(false);
+ }
+ return false;
+ }
+
+ private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 947b23f..8314855 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -20,6 +20,7 @@
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Space;
@@ -50,13 +51,14 @@
public QuickQSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
if (mFooter != null) {
- removeView((View) mFooter.getView());
+ removeView(mFooter.getView());
}
if (mTileLayout != null) {
for (int i = 0; i < mRecords.size(); i++) {
mTileLayout.removeTile(mRecords.get(i));
}
- removeView((View) mTileLayout);
+ View tileLayoutView = (View) mTileLayout;
+ ((ViewGroup) tileLayoutView.getParent()).removeView(tileLayoutView);
}
mTileLayout = new HeaderTileLayout(context);
mTileLayout.setListening(mListening);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 66cb59e..7bcaf13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -45,7 +45,6 @@
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
-
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.KeyguardStatusView;
@@ -670,7 +669,7 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mBlockTouches || mQs.isCustomizing()) {
+ if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) {
return false;
}
initDownStates(event);