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:
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,4 +158,7 @@ public abstract class NotificationViewWrapper implements TransformableView {
|
||||
|
||||
public void setShowingLegacyBackground(boolean showing) {
|
||||
}
|
||||
|
||||
public void setContentHeight(int contentHeight, int minHeightHint) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -272,7 +272,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
|
||||
|
||||
public boolean requestScrollTo() {
|
||||
findScrollContainer();
|
||||
mScrollContainer.scrollTo(mScrollContainerChild);
|
||||
mScrollContainer.lockScrollTo(mScrollContainerChild);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user