diff options
| author | 2018-01-04 10:49:55 -0500 | |
|---|---|---|
| committer | 2018-01-24 11:30:00 -0500 | |
| commit | 231b052d1f04d8b8835addc4f393f0e4fc58d5d2 (patch) | |
| tree | 135210761a721e3e65f24a0fa662b31884d3ace4 | |
| parent | d12ad36399e271b00d3aa0e98e07c2321e5fd0b2 (diff) | |
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
9 files changed, 148 insertions, 43 deletions
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index a648345e9c04..30d1352c8a01 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 @@ public interface QS extends FragmentBase { 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 b749a18a8c68..da9a6be40089 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 8f1880039857..95185c087f38 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 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha 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 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } 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 7320b861dbd5..6b0d592a4d01 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 @@ public class QSContainerImpl extends FrameLayout { @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 @@ public class QSContainerImpl extends FrameLayout { // 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 cdf0c0f32d9b..29a8f1641a85 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.support.annotation.VisibleForTesting; 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 @@ public class QSFragment extends Fragment implements QS { } @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 8f41084b2b2c..f7a03ae1f525 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.os.Message; 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 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne 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 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne protected QSSecurityFooter mFooter; private boolean mGridContentVisible = true; + private QSScrollLayout mScrollLayout; protected QSTileLayout mTileLayout; private QSCustomizer mCustomizePanel; @@ -95,18 +93,12 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne 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 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne 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 class QSPanel extends LinearLayout implements Tunable, Callback, Brightne 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 class QSPanel extends LinearLayout implements Tunable, Callback, Brightne 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 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne 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 000000000000..9a747877d9be --- /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 947b23f8f6c4..83148558ea1d 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.content.Context; 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 class QuickQSPanel extends QSPanel { 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 66cb59e3d317..7bcaf134e0f4 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.ViewTreeObserver; 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 @@ public class NotificationPanelView extends PanelView implements @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (mBlockTouches || mQs.isCustomizing()) { + if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) { return false; } initDownStates(event); |