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
This commit is contained in:
Adrian Roos
2016-05-05 17:45:44 -04:00
parent 99aa441581
commit 181385ce33
11 changed files with 193 additions and 11 deletions

View File

@@ -21,7 +21,7 @@
<com.android.internal.widget.NotificationActionListLayout
android:id="@+id/actions"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_height="@dimen/notification_action_list_height"
android:paddingEnd="4dp"
android:orientation="horizontal"
android:gravity="center_vertical"

View File

@@ -152,6 +152,9 @@
<!-- The margin on the end of the content view with a picture.-->
<dimen name="notification_content_picture_margin">56dp</dimen>
<!-- The height of the notification action list -->
<dimen name="notification_action_list_height">56dp</dimen>
<!-- height of the content margin to accomodate for the header -->
<dimen name="notification_content_margin_top">37.5dp</dimen>

View File

@@ -2584,6 +2584,8 @@
<java-symbol type="dimen" name="input_extract_action_button_width" />
<java-symbol type="dimen" name="input_extract_action_button_height" />
<java-symbol type="dimen" name="notification_action_list_height" />
<!-- TV Remote Service package -->
<java-symbol type="string" name="config_tvRemoteServicePackage" />

View File

@@ -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)

View File

@@ -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<ActionListTransformState> 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);
}
}

View File

@@ -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());
}
}
}

View File

@@ -158,4 +158,7 @@ public abstract class NotificationViewWrapper implements TransformableView {
public void setShowingLegacyBackground(boolean showing) {
}
public void setContentHeight(int contentHeight, int minHeightHint) {
}
}

View File

@@ -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);

View File

@@ -272,7 +272,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
public boolean requestScrollTo() {
findScrollContainer();
mScrollContainer.scrollTo(mScrollContainerChild);
mScrollContainer.lockScrollTo(mScrollContainerChild);
return true;
}

View File

@@ -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<NotificationStackScrollLayout, Float> BACKGROUND_FADE =
new FloatProperty<NotificationStackScrollLayout>("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();

View File

@@ -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.
*/