diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index ce3cbb1037c11..726176568605e 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -43,16 +43,11 @@ import java.util.ArrayList; public class NotificationHeaderView extends ViewGroup { private final int mChildMinWidth; private final int mContentEndMargin; - private View mAppName; - private View mHeaderText; - private View mSecondaryHeaderText; private OnClickListener mExpandClickListener; - private OnClickListener mFeedbackListener; private HeaderTouchListener mTouchListener = new HeaderTouchListener(); + private NotificationTopLineView mTopLineView; private NotificationExpandButton mExpandButton; private CachingIconView mIcon; - private View mProfileBadge; - private View mFeedbackIcon; private int mHeaderTextMarginEnd; private Drawable mBackground; private boolean mEntireHeaderClickable; @@ -94,13 +89,9 @@ public class NotificationHeaderView extends ViewGroup { @Override protected void onFinishInflate() { super.onFinishInflate(); - mAppName = findViewById(R.id.app_name_text); - mHeaderText = findViewById(R.id.header_text); - mSecondaryHeaderText = findViewById(R.id.header_text_secondary); - mExpandButton = findViewById(R.id.expand_button); mIcon = findViewById(R.id.icon); - mProfileBadge = findViewById(R.id.profile_badge); - mFeedbackIcon = findViewById(R.id.feedback); + mTopLineView = findViewById(R.id.notification_top_line); + mExpandButton = findViewById(R.id.expand_button); setClipToPadding(false); } @@ -127,7 +118,7 @@ public class NotificationHeaderView extends ViewGroup { lp.topMargin + lp.bottomMargin, lp.height); child.measure(childWidthSpec, childHeightSpec); // Icons that should go at the end - if (child == mExpandButton || child == mProfileBadge || child == mFeedbackIcon) { + if (child == mExpandButton) { iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth(); } else { totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth(); @@ -138,16 +129,9 @@ public class NotificationHeaderView extends ViewGroup { int endMargin = Math.max(mHeaderTextMarginEnd, iconWidth); if (totalWidth > givenWidth - endMargin) { int overFlow = totalWidth - givenWidth + endMargin; - // We are overflowing, lets shrink the app name first - overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mAppName, + // We are overflowing; shrink the top line + shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mTopLineView, mChildMinWidth); - - // still overflowing, we shrink the header text - overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mHeaderText, 0); - - // still overflowing, finally we shrink the secondary header text - shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mSecondaryHeaderText, - 0); } setMeasuredDimension(givenWidth, givenHeight); } @@ -183,7 +167,7 @@ public class NotificationHeaderView extends ViewGroup { int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f); int bottom = top + childHeight; // Icons that should go at the end - if (child == mExpandButton || child == mProfileBadge || child == mFeedbackIcon) { + if (child == mExpandButton) { if (end == getMeasuredWidth()) { layoutRight = end - mContentEndMargin; } else { @@ -251,7 +235,7 @@ public class NotificationHeaderView extends ViewGroup { } private void updateTouchListener() { - if (mExpandClickListener == null && mFeedbackListener == null) { + if (mExpandClickListener == null) { setOnTouchListener(null); return; } @@ -259,15 +243,6 @@ public class NotificationHeaderView extends ViewGroup { mTouchListener.bindTouchRects(); } - /** - * Sets onclick listener for feedback icon. - */ - public void setFeedbackOnClickListener(OnClickListener l) { - mFeedbackListener = l; - mFeedbackIcon.setOnClickListener(mFeedbackListener); - updateTouchListener(); - } - @Override public void setOnClickListener(@Nullable OnClickListener l) { mExpandClickListener = l; @@ -302,7 +277,6 @@ public class NotificationHeaderView extends ViewGroup { private final ArrayList mTouchRects = new ArrayList<>(); private Rect mExpandButtonRect; - private Rect mFeedbackRect; private int mTouchSlop; private boolean mTrackGesture; private float mDownX; @@ -315,7 +289,6 @@ public class NotificationHeaderView extends ViewGroup { mTouchRects.clear(); addRectAroundView(mIcon); mExpandButtonRect = addRectAroundView(mExpandButton); - mFeedbackRect = addRectAroundView(mFeedbackIcon); addWidthRect(); mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } @@ -376,11 +349,11 @@ public class NotificationHeaderView extends ViewGroup { break; case MotionEvent.ACTION_UP: if (mTrackGesture) { - if (mFeedbackIcon.isVisibleToUser() - && (mFeedbackRect.contains((int) x, (int) y) - || mFeedbackRect.contains((int) mDownX, (int) mDownY))) { - mFeedbackIcon.performClick(); - return true; + float topLineX = mTopLineView.getX(); + float topLineY = mTopLineView.getY(); + if (mTopLineView.onTouchUp(x - topLineX, y - topLineY, + mDownX - topLineX, mDownY - topLineY)) { + break; } mExpandButton.performClick(); } @@ -402,7 +375,9 @@ public class NotificationHeaderView extends ViewGroup { return true; } } - return false; + float topLineX = x - mTopLineView.getX(); + float topLineY = y - mTopLineView.getY(); + return mTopLineView.isInTouchRect(topLineX, topLineY); } } diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java new file mode 100644 index 0000000000000..9443ccfc75534 --- /dev/null +++ b/core/java/android/view/NotificationTopLineView.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2015 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 android.view; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.widget.RemoteViews; + +import com.android.internal.R; + +import java.util.Arrays; +import java.util.List; + +/** + * The top line of content in a notification view. + * This includes the text views and badges but excludes the icon and the expander. + * + * @hide + */ +@RemoteViews.RemoteView +public class NotificationTopLineView extends ViewGroup { + private final int mChildMinWidth; + private final int mContentEndMargin; + private View mAppName; + private View mHeaderText; + private View mSecondaryHeaderText; + private OnClickListener mFeedbackListener; + private HeaderTouchListener mTouchListener = new HeaderTouchListener(); + private View mProfileBadge; + private View mFeedbackIcon; + private int mHeaderTextMarginEnd; + private List mIconsAtEnd; + + public NotificationTopLineView(Context context) { + this(context, null); + } + + public NotificationTopLineView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public NotificationTopLineView(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public NotificationTopLineView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + Resources res = getResources(); + mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width); + mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mAppName = findViewById(R.id.app_name_text); + mHeaderText = findViewById(R.id.header_text); + mSecondaryHeaderText = findViewById(R.id.header_text_secondary); + mProfileBadge = findViewById(R.id.profile_badge); + mFeedbackIcon = findViewById(R.id.feedback); + mIconsAtEnd = Arrays.asList(mProfileBadge, mFeedbackIcon); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int givenWidth = MeasureSpec.getSize(widthMeasureSpec); + final int givenHeight = MeasureSpec.getSize(heightMeasureSpec); + int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth, + MeasureSpec.AT_MOST); + int wrapContentHeightSpec = MeasureSpec.makeMeasureSpec(givenHeight, + MeasureSpec.AT_MOST); + int totalWidth = getPaddingStart(); + int iconWidth = getPaddingEnd(); + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + if (child.getVisibility() == GONE) { + // We'll give it the rest of the space in the end + continue; + } + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + int childWidthSpec = getChildMeasureSpec(wrapContentWidthSpec, + lp.leftMargin + lp.rightMargin, lp.width); + int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec, + lp.topMargin + lp.bottomMargin, lp.height); + child.measure(childWidthSpec, childHeightSpec); + // Icons that should go at the end + if (mIconsAtEnd.contains(child)) { + iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth(); + } else { + totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth(); + } + } + + // Ensure that there is at least enough space for the icons + int endMargin = Math.max(mHeaderTextMarginEnd, iconWidth); + if (totalWidth > givenWidth - endMargin) { + int overFlow = totalWidth - givenWidth + endMargin; + // We are overflowing, lets shrink the app name first + overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mAppName, + mChildMinWidth); + + // still overflowing, we shrink the header text + overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mHeaderText, 0); + + // still overflowing, finally we shrink the secondary header text + shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mSecondaryHeaderText, + 0); + } + setMeasuredDimension(givenWidth, givenHeight); + } + + private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView, + int minimumWidth) { + final int oldWidth = targetView.getMeasuredWidth(); + if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) { + // we're still too big + int newSize = Math.max(minimumWidth, oldWidth - overFlow); + int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST); + targetView.measure(childWidthSpec, heightSpec); + overFlow -= oldWidth - newSize; + } + return overFlow; + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int left = getPaddingStart(); + int end = getMeasuredWidth(); + int childCount = getChildCount(); + int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } + int childHeight = child.getMeasuredHeight(); + MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams(); + int layoutLeft; + int layoutRight; + int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f); + int bottom = top + childHeight; + // Icons that should go at the end + if (mIconsAtEnd.contains(child)) { + if (end == getMeasuredWidth()) { + layoutRight = end - mContentEndMargin; + } else { + layoutRight = end - params.getMarginEnd(); + } + layoutLeft = layoutRight - child.getMeasuredWidth(); + end = layoutLeft - params.getMarginStart(); + } else { + left += params.getMarginStart(); + int right = left + child.getMeasuredWidth(); + layoutLeft = left; + layoutRight = right; + left = right + params.getMarginEnd(); + } + if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { + int ltrLeft = layoutLeft; + layoutLeft = getWidth() - layoutRight; + layoutRight = getWidth() - ltrLeft; + } + child.layout(layoutLeft, top, layoutRight, bottom); + } + updateTouchListener(); + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new MarginLayoutParams(getContext(), attrs); + } + + private void updateTouchListener() { + if (mFeedbackListener == null) { + setOnTouchListener(null); + return; + } + setOnTouchListener(mTouchListener); + mTouchListener.bindTouchRects(); + } + + /** + * Sets onclick listener for feedback icon. + */ + public void setFeedbackOnClickListener(OnClickListener l) { + mFeedbackListener = l; + mFeedbackIcon.setOnClickListener(mFeedbackListener); + updateTouchListener(); + } + + /** + * Sets the margin end for the text portion of the header, excluding right-aligned elements + * + * @param headerTextMarginEnd margin size + */ + public void setHeaderTextMarginEnd(int headerTextMarginEnd) { + if (mHeaderTextMarginEnd != headerTextMarginEnd) { + mHeaderTextMarginEnd = headerTextMarginEnd; + requestLayout(); + } + } + + /** + * Get the current margin end value for the header text + * + * @return margin size + */ + public int getHeaderTextMarginEnd() { + return mHeaderTextMarginEnd; + } + + private class HeaderTouchListener implements OnTouchListener { + + private Rect mFeedbackRect; + private int mTouchSlop; + private boolean mTrackGesture; + private float mDownX; + private float mDownY; + + HeaderTouchListener() { + } + + public void bindTouchRects() { + mFeedbackRect = getRectAroundView(mFeedbackIcon); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + } + + private Rect getRectAroundView(View view) { + float size = 48 * getResources().getDisplayMetrics().density; + float width = Math.max(size, view.getWidth()); + float height = Math.max(size, view.getHeight()); + final Rect r = new Rect(); + if (view.getVisibility() == GONE) { + view = getFirstChildNotGone(); + r.left = (int) (view.getLeft() - width / 2.0f); + } else { + r.left = (int) ((view.getLeft() + view.getRight()) / 2.0f - width / 2.0f); + } + r.top = (int) ((view.getTop() + view.getBottom()) / 2.0f - height / 2.0f); + r.bottom = (int) (r.top + height); + r.right = (int) (r.left + width); + return r; + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + switch (event.getActionMasked() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + mTrackGesture = false; + if (isInside(x, y)) { + mDownX = x; + mDownY = y; + mTrackGesture = true; + return true; + } + break; + case MotionEvent.ACTION_MOVE: + if (mTrackGesture) { + if (Math.abs(mDownX - x) > mTouchSlop + || Math.abs(mDownY - y) > mTouchSlop) { + mTrackGesture = false; + } + } + break; + case MotionEvent.ACTION_UP: + if (mTrackGesture && onTouchUp(x, y, mDownX, mDownY)) { + return true; + } + break; + } + return mTrackGesture; + } + + private boolean onTouchUp(float upX, float upY, float downX, float downY) { + if (mFeedbackIcon.isVisibleToUser() + && (mFeedbackRect.contains((int) upX, (int) upY) + || mFeedbackRect.contains((int) downX, (int) downY))) { + mFeedbackIcon.performClick(); + return true; + } + return false; + } + + private boolean isInside(float x, float y) { + return mFeedbackRect.contains((int) x, (int) y); + } + } + + private View getFirstChildNotGone() { + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + return child; + } + } + return this; + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } + + /** + * Determine if the given point is touching an active part of the top line. + */ + public boolean isInTouchRect(float x, float y) { + if (mFeedbackListener == null) { + return false; + } + return mTouchListener.isInside(x, y); + } + + /** + * Perform a click on an active part of the top line, if touching. + */ + public boolean onTouchUp(float upX, float upY, float downX, float downY) { + if (mFeedbackListener == null) { + return false; + } + return mTouchListener.onTouchUp(upX, upY, downX, downY); + } +} diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 88493c9505edb..d11875e0f8903 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -38,81 +38,7 @@ android:layout_gravity="center" /> - - - - - - - - + - - - diff --git a/core/res/res/layout/notification_template_top_line.xml b/core/res/res/layout/notification_template_top_line.xml new file mode 100644 index 0000000000000..27fab859a0456 --- /dev/null +++ b/core/res/res/layout/notification_template_top_line.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d7cb8f89f23cb..f59053b7f424f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2850,6 +2850,7 @@ + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 0fc4c42599cf7..5aeacaba6f644 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -22,8 +22,10 @@ import android.app.Notification; import android.content.Context; import android.util.ArraySet; import android.view.NotificationHeaderView; +import android.view.NotificationTopLineView; import android.view.View; import android.view.ViewGroup; +import android.view.ViewGroup.MarginLayoutParams; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import android.widget.FrameLayout; @@ -44,7 +46,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import java.util.Stack; /** - * Wraps a notification header view. + * Wraps a notification view which may or may not include a header. */ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { @@ -56,6 +58,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { private CachingIconView mIcon; private NotificationExpandButton mExpandButton; protected NotificationHeaderView mNotificationHeader; + protected NotificationTopLineView mNotificationTopLine; private TextView mHeaderText; private TextView mAppNameText; private ImageView mWorkProfileImage; @@ -107,14 +110,15 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button); mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge); mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header); + mNotificationTopLine = mView.findViewById(com.android.internal.R.id.notification_top_line); mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon); mFeedbackIcon = mView.findViewById(com.android.internal.R.id.feedback); } private void addFeedbackOnClickListener(ExpandableNotificationRow row) { View.OnClickListener listener = row.getFeedbackOnClickListener(); - if (mNotificationHeader != null) { - mNotificationHeader.setFeedbackOnClickListener(listener); + if (mNotificationTopLine != null) { + mNotificationTopLine.setFeedbackOnClickListener(listener); } if (mFeedbackIcon != null) { mFeedbackIcon.setOnClickListener(listener); @@ -158,13 +162,11 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mAppNameText.setTextAppearance( com.android.internal.R.style .TextAppearance_DeviceDefault_Notification_Conversation_AppName); - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mAppNameText.getLayoutParams(); layoutParams.setMarginStart(0); } if (mIconContainer != null) { - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mIconContainer.getLayoutParams(); layoutParams.width = mIconContainer.getContext().getResources().getDimensionPixelSize( com.android.internal.R.dimen.conversation_content_start); @@ -174,8 +176,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { layoutParams.setMarginStart(marginStart * -1); } if (mIcon != null) { - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mIcon.getLayoutParams(); layoutParams.setMarginEnd(0); } } @@ -187,21 +188,18 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { com.android.internal.R.attr.notificationHeaderTextAppearance, com.android.internal.R.style.TextAppearance_DeviceDefault_Notification_Info); mAppNameText.setTextAppearance(textAppearance); - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mAppNameText.getLayoutParams(); final int marginStart = mAppNameText.getContext().getResources().getDimensionPixelSize( com.android.internal.R.dimen.notification_header_app_name_margin_start); layoutParams.setMarginStart(marginStart); } if (mIconContainer != null) { - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mIconContainer.getLayoutParams(); layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT; layoutParams.setMarginStart(0); } if (mIcon != null) { - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mIcon.getLayoutParams(); final int marginEnd = mIcon.getContext().getResources().getDimensionPixelSize( com.android.internal.R.dimen.notification_header_icon_margin_end); layoutParams.setMarginEnd(marginEnd); @@ -261,6 +259,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { @Override public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) { mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE); + mExpandButton.setOnClickListener(expandable ? onClickListener : null); if (mNotificationHeader != null) { mNotificationHeader.setOnClickListener(expandable ? onClickListener : null); }