From 181385ce33c83a249087a1cff65870dfe120aae8 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Thu, 5 May 2016 17:45:44 -0400 Subject: [PATCH] Keep RemoteInputView visible when focused Scrolls the NotificationStackScrollLayout in response to movement to ensure that the focused view stays visible. Also makes sure that the action list is always aligned at the bottom of the notification to avoid jank during this scrolling when the height changes. Fixes: 28193862 Fixes: 26919632 Change-Id: I911a873367fe26eafd9fae4bca4e693d0827eba7 --- .../notification_material_action_list.xml | 2 +- core/res/res/values/dimens.xml | 3 + core/res/res/values/symbols.xml | 2 + .../statusbar/NotificationContentView.java | 46 ++++++++++++-- .../ActionListTransformState.java | 60 +++++++++++++++++++ .../NotificationTemplateViewWrapper.java | 22 +++++++ .../notification/NotificationViewWrapper.java | 3 + .../notification/TransformState.java | 7 ++- .../statusbar/policy/RemoteInputView.java | 2 +- .../stack/NotificationStackScrollLayout.java | 51 +++++++++++++++- .../statusbar/stack/ScrollContainer.java | 6 ++ 11 files changed, 193 insertions(+), 11 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/ActionListTransformState.java diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml index 547d3cafb621a..4670dcabe2118 100644 --- a/core/res/res/layout/notification_material_action_list.xml +++ b/core/res/res/layout/notification_material_action_list.xml @@ -21,7 +21,7 @@ 56dp + + 56dp + 37.5dp diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c64a93485b079..29818df192e44 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2584,6 +2584,8 @@ + + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 34c9c0de44596..078b3c6bbdea6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -374,10 +374,42 @@ public class NotificationContentView extends FrameLayout { mContentHeight = Math.max(Math.min(contentHeight, getHeight()), getMinHeight());; mUnrestrictedContentHeight = Math.max(contentHeight, getMinHeight()); selectLayout(mAnimate /* animate */, false /* force */); + + int minHeightHint = getMinContentHeightHint(); + + NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType); + if (wrapper != null) { + wrapper.setContentHeight(mContentHeight, minHeightHint); + } + + wrapper = getVisibleWrapper(mTransformationStartVisibleType); + if (wrapper != null) { + wrapper.setContentHeight(mContentHeight, minHeightHint); + } + updateClipping(); invalidateOutline(); } + /** + * @return the minimum apparent height that the wrapper should allow for the purpose + * of aligning elements at the bottom edge. If this is larger than the content + * height, the notification is clipped instead of being further shrunk. + */ + private int getMinContentHeightHint() { + if (mIsChildInGroup && (mVisibleType == VISIBLE_TYPE_SINGLELINE + || mTransformationStartVisibleType == VISIBLE_TYPE_SINGLELINE)) { + return mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_action_list_height); + } + if (mHeadsUpChild != null) { + return mHeadsUpChild.getHeight(); + } else { + return mContractedChild.getHeight() + mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_action_list_height); + } + } + private void updateContentTransformation() { int visibleType = calculateVisibleType(); if (visibleType != mVisibleType) { @@ -493,11 +525,15 @@ public class NotificationContentView extends FrameLayout { } else { int visibleType = calculateVisibleType(); if (visibleType != mVisibleType || force) { - View visibleView = getViewForVisibleType(visibleType); - if (visibleView != null) { - visibleView.setVisibility(VISIBLE); - transferRemoteInputFocus(visibleType); - } + View visibleView = getViewForVisibleType(visibleType); + if (visibleView != null) { + visibleView.setVisibility(VISIBLE); + transferRemoteInputFocus(visibleType); + } + NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType); + if (visibleWrapper != null) { + visibleWrapper.setContentHeight(mContentHeight, getMinContentHeightHint()); + } if (animate && ((visibleType == VISIBLE_TYPE_EXPANDED && mExpandedChild != null) || (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActionListTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActionListTransformState.java new file mode 100644 index 0000000000000..c0373bef0f5ae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActionListTransformState.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.notification; + +import android.text.Layout; +import android.text.TextUtils; +import android.util.Pools; +import android.view.View; +import android.widget.TextView; + +/** + * A transform state of the action list +*/ +public class ActionListTransformState extends TransformState { + + private static Pools.SimplePool sInstancePool + = new Pools.SimplePool<>(40); + + @Override + protected boolean sameAs(TransformState otherState) { + return otherState instanceof ActionListTransformState; + } + + public static ActionListTransformState obtain() { + ActionListTransformState instance = sInstancePool.acquire(); + if (instance != null) { + return instance; + } + return new ActionListTransformState(); + } + + @Override + protected void resetTransformedView() { + // We need to keep the Y transformation, because this is used to keep the action list + // aligned at the bottom, unrelated to transforms. + float y = getTransformedView().getTranslationY(); + super.resetTransformedView(); + getTransformedView().setTranslationY(y); + } + + @Override + public void recycle() { + super.recycle(); + sInstancePool.release(this); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java index 889bd5cac7a54..7ca2df99d76eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java @@ -41,6 +41,10 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp private ProgressBar mProgressBar; private TextView mTitle; private TextView mText; + private View mActionsContainer; + + private int mContentHeight; + private int mMinHeightHint; protected NotificationTemplateViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { super(ctx, view, row); @@ -123,6 +127,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp // It's still a viewstub mProgressBar = null; } + mActionsContainer = mView.findViewById(com.android.internal.R.id.actions_container); } @Override @@ -225,4 +230,21 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp (int) (gSource * (1f - t) + gTarget * t), (int) (bSource * (1f - t) + bTarget * t)); } + + @Override + public void setContentHeight(int contentHeight, int minHeightHint) { + super.setContentHeight(contentHeight, minHeightHint); + + mContentHeight = contentHeight; + mMinHeightHint = minHeightHint; + updateActionOffset(); + } + + private void updateActionOffset() { + if (mActionsContainer != null) { + // We should never push the actions higher than they are in the headsup view. + int constrainedContentHeight = Math.max(mContentHeight, mMinHeightHint); + mActionsContainer.setTranslationY(constrainedContentHeight - mView.getHeight()); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java index 7a0df1f54c64e..22519e6e4de37 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java @@ -158,4 +158,7 @@ public abstract class NotificationViewWrapper implements TransformableView { public void setShowingLegacyBackground(boolean showing) { } + + public void setContentHeight(int contentHeight, int minHeightHint) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java index 8207215ec4a9a..4e643f0fcae19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java @@ -374,6 +374,11 @@ public class TransformState { result.initFrom(view); return result; } + if (view.getId() == com.android.internal.R.id.actions_container) { + ActionListTransformState result = ActionListTransformState.obtain(); + result.initFrom(view); + return result; + } if (view instanceof NotificationHeaderView) { HeaderTransformState result = HeaderTransformState.obtain(); result.initFrom(view); @@ -467,7 +472,7 @@ public class TransformState { resetTransformedView(); } - private void resetTransformedView() { + protected void resetTransformedView() { mTransformedView.setTranslationX(0); mTransformedView.setTranslationY(0); mTransformedView.setScaleX(1.0f); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index a855aed886945..095265a0f3298 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -272,7 +272,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene public boolean requestScrollTo() { findScrollContainer(); - mScrollContainer.scrollTo(mScrollContainerChild); + mScrollContainer.lockScrollTo(mScrollContainerChild); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 80eec7ee6fd13..c66cfba3bb507 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -243,6 +243,7 @@ public class NotificationStackScrollLayout extends ViewGroup = new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { + updateForcedScroll(); updateChildren(); mChildrenUpdateRequested = false; getViewTreeObserver().removeOnPreDrawListener(this); @@ -334,6 +335,7 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mDrawBackgroundAsSrc; private boolean mFadedOut; private boolean mGroupExpandedForMeasure; + private View mForcedScroll; private float mBackgroundFadeAmount = 1.0f; private static final Property BACKGROUND_FADE = new FloatProperty("backgroundFade") { @@ -591,6 +593,23 @@ public class NotificationStackScrollLayout extends ViewGroup clampScrollPosition(); } + private void updateForcedScroll() { + if (mForcedScroll != null && (!mForcedScroll.hasFocus() + || !mForcedScroll.isAttachedToWindow())) { + mForcedScroll = null; + } + if (mForcedScroll != null) { + ExpandableView expandableView = (ExpandableView) mForcedScroll; + int positionInLinearLayout = getPositionInLinearLayout(expandableView); + int targetScroll = targetScrollForView(expandableView, positionInLinearLayout); + + targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange())); + if (mOwnScrollY < targetScroll || positionInLinearLayout < mOwnScrollY) { + mOwnScrollY = targetScroll; + } + } + } + private void requestChildrenUpdate() { if (!mChildrenUpdateRequested) { getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater); @@ -978,11 +997,19 @@ public class NotificationStackScrollLayout extends ViewGroup mScrollingEnabled = enable; } + @Override + public void lockScrollTo(View v) { + if (mForcedScroll == v) { + return; + } + mForcedScroll = v; + scrollTo(v); + } + + @Override public boolean scrollTo(View v) { ExpandableView expandableView = (ExpandableView) v; - int positionInLinearLayout = getPositionInLinearLayout(v); - int targetScroll = positionInLinearLayout + expandableView.getIntrinsicHeight() + - getImeInset() - getHeight() + getTopPadding(); + int targetScroll = targetScrollForView(expandableView, getPositionInLinearLayout(v)); if (mOwnScrollY < targetScroll) { mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY); @@ -993,6 +1020,15 @@ public class NotificationStackScrollLayout extends ViewGroup return false; } + /** + * @return the scroll necessary to make the bottom edge of {@param v} align with the top of + * the IME. + */ + private int targetScrollForView(ExpandableView v, int positionInLinearLayout) { + return positionInLinearLayout + v.getIntrinsicHeight() + + getImeInset() - getHeight() + getTopPadding(); + } + @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { mBottomInset = insets.getSystemWindowInsetBottom(); @@ -1111,6 +1147,7 @@ public class NotificationStackScrollLayout extends ViewGroup if (ev.getY() < mQsContainer.getBottom()) { return false; } + mForcedScroll = null; initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); @@ -2786,6 +2823,14 @@ public class NotificationStackScrollLayout extends ViewGroup } } + @Override + public void clearChildFocus(View child) { + super.clearChildFocus(child); + if (mForcedScroll == child) { + mForcedScroll = null; + } + } + @Override public void requestDisallowLongPress() { removeLongPressCallback(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java index 17b7871bbfe02..b9d12ce8f1510 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java @@ -35,6 +35,12 @@ public interface ScrollContainer { */ boolean scrollTo(View v); + /** + * Like {@link #scrollTo(View)}, but keeps the scroll locked until the user + * scrolls, or {@param v} loses focus or is detached. + */ + void lockScrollTo(View v); + /** * Request that the view does not dismiss for the current touch. */