diff options
30 files changed, 917 insertions, 144 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index d8e1c895a6eb..c6568e16086c 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5577,6 +5577,24 @@ public class Notification implements Parcelable public void setRebuildStyledRemoteViews(boolean rebuild) { mRebuildStyledRemoteViews = rebuild; } + + /** + * Get the text that should be displayed in the statusBar when heads upped. This is + * usually just the app name, but may be different depending on the style. + * + * @param publicMode If true, return a text that is safe to display in public. + * + * @hide + */ + public CharSequence getHeadsUpStatusBarText(boolean publicMode) { + if (mStyle != null && !publicMode) { + CharSequence text = mStyle.getHeadsUpStatusBarText(); + if (!TextUtils.isEmpty(text)) { + return text; + } + } + return loadHeaderAppName(); + } } /** @@ -5954,6 +5972,16 @@ public class Notification implements Parcelable * @hide */ public abstract boolean areNotificationsVisiblyDifferent(Style other); + + /** + * @return the the text that should be displayed in the statusBar when heads-upped. + * If {@code null} is returned, the default implementation will be used. + * + * @hide + */ + public CharSequence getHeadsUpStatusBarText() { + return null; + } } /** @@ -6403,6 +6431,23 @@ public class Notification implements Parcelable } /** + * @return the the text that should be displayed in the statusBar when heads upped. + * If {@code null} is returned, the default implementation will be used. + * + * @hide + */ + @Override + public CharSequence getHeadsUpStatusBarText() { + CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) + ? super.mBigContentTitle + : mConversationTitle; + if (!TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) { + return conversationTitle; + } + return null; + } + + /** * @return the user to be displayed for any replies sent by the user */ public Person getUser() { diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ad4d7ddfc269..ac01c4efc3ea 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3232,6 +3232,7 @@ <java-symbol type="id" name="remote_input_progress" /> <java-symbol type="id" name="remote_input_send" /> <java-symbol type="id" name="remote_input" /> + <java-symbol type="dimen" name="notification_content_margin" /> <java-symbol type="dimen" name="slice_shortcut_size" /> <java-symbol type="dimen" name="slice_icon_size" /> <java-symbol type="dimen" name="slice_padding" /> diff --git a/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml b/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml new file mode 100644 index 000000000000..999af23eae0c --- /dev/null +++ b/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<com.android.systemui.statusbar.HeadsUpStatusBarView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:visibility="invisible" + android:id="@+id/heads_up_status_bar_view" +> + <!-- This is a space just used as a layout and it's not actually displaying anything. We're + repositioning the statusbar icon to the position where this is laid out when showing this + view. --> + <Space + android:id="@+id/icon_placeholder" + android:layout_width="@dimen/status_bar_icon_drawing_size" + android:layout_height="@dimen/status_bar_icon_drawing_size" + android:layout_gravity="center_vertical" + /> + <TextView + android:id="@+id/text" + android:textAppearance="@style/TextAppearance.HeadsUpStatusBarText" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="marquee" + android:fadingEdge="horizontal" + android:textAlignment="viewStart" + android:paddingStart="6dp" + android:layout_weight="1" + android:layout_gravity="center_vertical" + /> + +</com.android.systemui.statusbar.HeadsUpStatusBarView> diff --git a/packages/SystemUI/res/layout/notification_icon_area.xml b/packages/SystemUI/res/layout/notification_icon_area.xml index 6732e6c62e8e..fa696cc1f54c 100644 --- a/packages/SystemUI/res/layout/notification_icon_area.xml +++ b/packages/SystemUI/res/layout/notification_icon_area.xml @@ -18,12 +18,14 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/notification_icon_area_inner" android:layout_width="match_parent" - android:layout_height="match_parent" > + android:layout_height="match_parent" + android:clipChildren="false"> <com.android.systemui.statusbar.phone.NotificationIconContainer android:id="@+id/notificationIcons" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentStart="true" android:gravity="center_vertical" - android:orientation="horizontal"/> + android:orientation="horizontal" + android:clipChildren="false"/> </com.android.keyguard.AlphaOptimizedLinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 8c0b9bab35a0..9d336e25985f 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -3,16 +3,16 @@ ** ** Copyright 2006, 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 +** 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 +** 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 +** 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. */ --> @@ -34,7 +34,7 @@ android:id="@+id/notification_lights_out" android:layout_width="@dimen/status_bar_icon_size" android:layout_height="match_parent" - android:paddingStart="6dip" + android:paddingStart="@dimen/status_bar_padding_start" android:paddingBottom="2dip" android:src="@drawable/ic_sysbar_lights_out_dot_small" android:scaleType="center" @@ -44,7 +44,7 @@ <LinearLayout android:id="@+id/status_bar_contents" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingStart="6dp" + android:paddingStart="@dimen/status_bar_padding_start" android:paddingEnd="8dp" android:orientation="horizontal" > @@ -53,33 +53,41 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout="@layout/operator_name" /> - - <LinearLayout + <FrameLayout android:layout_height="match_parent" android:layout_width="0dp" - android:layout_weight="1" - > - <com.android.systemui.statusbar.policy.Clock - android:id="@+id/clock" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:textAppearance="@style/TextAppearance.StatusBar.Clock" - android:singleLine="true" - android:paddingStart="@dimen/status_bar_left_clock_starting_padding" - android:paddingEnd="@dimen/status_bar_left_clock_end_padding" - android:gravity="center_vertical|start" - /> + android:layout_weight="1"> - <!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and - PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). --> - <com.android.systemui.statusbar.AlphaOptimizedFrameLayout - android:id="@+id/notification_icon_area" - android:layout_width="0dp" + <include layout="@layout/heads_up_status_bar_layout" /> + + <LinearLayout android:layout_height="match_parent" - android:layout_weight="1" - android:orientation="horizontal" /> + android:layout_width="match_parent" + android:clipChildren="false" + > + <com.android.systemui.statusbar.policy.Clock + android:id="@+id/clock" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:textAppearance="@style/TextAppearance.StatusBar.Clock" + android:singleLine="true" + android:paddingStart="@dimen/status_bar_left_clock_starting_padding" + android:paddingEnd="@dimen/status_bar_left_clock_end_padding" + android:gravity="center_vertical|start" + /> + + <!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and + PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). --> + <com.android.systemui.statusbar.AlphaOptimizedFrameLayout + android:id="@+id/notification_icon_area" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:orientation="horizontal" + android:clipChildren="false"/> - </LinearLayout> + </LinearLayout> + </FrameLayout> <!-- Space should cover the notch (if it exists) and let other views lay out around it --> <android.widget.Space diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index cb3c282f703b..9f19603a0129 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -110,6 +110,9 @@ <!-- Side padding on the lockscreen on the side of notifications --> <dimen name="notification_side_paddings">4dp</dimen> + <!-- padding between the heads up and the statusbar --> + <dimen name="heads_up_status_bar_padding">8dp</dimen> + <!-- Height of a messaging notifications with actions at least. Not that this is an upper bound and the notification won't use this much, but is measured with wrap_content --> <dimen name="notification_messaging_actions_min_height">196dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 1470dfa4d05d..20d883435aef 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -490,6 +490,10 @@ <item name="android:paddingEnd">8dp</item> </style> + <style name="TextAppearance.HeadsUpStatusBarText" + parent="@*android:style/TextAppearance.Material.Notification.Info"> + </style> + <style name="edit_theme" parent="qs_base"> <item name="android:colorBackground">?android:attr/colorSecondary</item> </style> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index be28d8cea776..4001f9e04a79 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -139,6 +139,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mSensitiveHiddenInGeneral; private boolean mShowingPublicInitialized; private boolean mHideSensitiveForIntrinsicHeight; + private float mHeaderVisibleAmount = 1.0f; /** * Is this notification expanded by the system. The expansion state can be overridden by the @@ -156,8 +157,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private NotificationContentView mPublicLayout; private NotificationContentView mPrivateLayout; private NotificationContentView[] mLayouts; - private int mMaxExpandHeight; - private int mHeadsUpHeight; private int mNotificationColor; private ExpansionLogger mLogger; private String mLoggingKey; @@ -534,6 +533,30 @@ public class ExpandableNotificationRow extends ActivatableNotificationView addChildNotification(row, -1); } + /** + * Set the how much the header should be visible. A value of 0 will make the header fully gone + * and a value of 1 will make the notification look just like normal. + * This is being used for heads up notifications, when they are pinned to the top of the screen + * and the header content is extracted to the statusbar. + * + * @param headerVisibleAmount the amount the header should be visible. + */ + public void setHeaderVisibleAmount(float headerVisibleAmount) { + if (mHeaderVisibleAmount != headerVisibleAmount) { + mHeaderVisibleAmount = headerVisibleAmount; + mPrivateLayout.setHeaderVisibleAmount(headerVisibleAmount); + if (mChildrenContainer != null) { + mChildrenContainer.setHeaderVisibleAmount(headerVisibleAmount); + } + notifyHeightChanged(false /* needsAnimation */); + } + } + + @VisibleForTesting + public float getHeaderVisibleAmount() { + return mHeaderVisibleAmount; + } + @Override public void setHeadsUpIsVisible() { super.setHeadsUpIsVisible(); @@ -742,11 +765,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mChildrenContainer.getIntrinsicHeight(); } if(mExpandedWhenPinned) { - return Math.max(getMaxExpandHeight(), mHeadsUpHeight); + return Math.max(getMaxExpandHeight(), getHeadsUpHeight()); } else if (atLeastMinHeight) { - return Math.max(getCollapsedHeight(), mHeadsUpHeight); + return Math.max(getCollapsedHeight(), getHeadsUpHeight()); } else { - return mHeadsUpHeight; + return getHeadsUpHeight(); } } @@ -1923,9 +1946,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (isPinned() || mHeadsupDisappearRunning) { return getPinnedHeadsUpHeight(true /* atLeastMinHeight */); } else if (isExpanded()) { - return Math.max(getMaxExpandHeight(), mHeadsUpHeight); + return Math.max(getMaxExpandHeight(), getHeadsUpHeight()); } else { - return Math.max(getCollapsedHeight(), mHeadsUpHeight); + return Math.max(getCollapsedHeight(), getHeadsUpHeight()); } } else if (isExpanded()) { return getMaxExpandHeight(); @@ -1999,8 +2022,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int intrinsicBefore = getIntrinsicHeight(); super.onLayout(changed, left, top, right, bottom); - updateMaxHeights(); + if (intrinsicBefore != getIntrinsicHeight()) { + notifyHeightChanged(true /* needsAnimation */); + } if (mMenuRow.getMenuView() != null) { mMenuRow.onHeightUpdate(); } @@ -2024,15 +2050,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - public void updateMaxHeights() { - int intrinsicBefore = getIntrinsicHeight(); - mMaxExpandHeight = mPrivateLayout.getExpandHeight(); - mHeadsUpHeight = mPrivateLayout.getHeadsUpHeight(); - if (intrinsicBefore != getIntrinsicHeight()) { - notifyHeightChanged(true /* needsAnimation */); - } - } - @Override public void notifyHeightChanged(boolean needsAnimation) { super.notifyHeightChanged(needsAnimation); @@ -2175,7 +2192,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public int getMaxExpandHeight() { - return mMaxExpandHeight; + return mPrivateLayout.getExpandHeight(); + } + + + private int getHeadsUpHeight() { + return mPrivateLayout.getHeadsUpHeight(); } public boolean areGutsExposed() { @@ -2275,7 +2297,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) { return mChildrenContainer.getMinHeight(); } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp) { - return mHeadsUpHeight; + return getHeadsUpHeight(); } NotificationContentView showingLayout = getShowingLayout(); return showingLayout.getMinHeight(); @@ -2321,10 +2343,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - public boolean isMaxExpandHeightInitialized() { - return mMaxExpandHeight != 0; - } - public NotificationContentView getShowingLayout() { return shouldShowPublic() ? mPublicLayout : mPrivateLayout; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java new file mode 100644 index 000000000000..9977fe95d5c6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java @@ -0,0 +1,133 @@ +/* + * 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.statusbar; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.keyguard.AlphaOptimizedLinearLayout; +import com.android.systemui.R; +import com.android.systemui.statusbar.policy.DarkIconDispatcher; + +/** + * The view in the statusBar that contains part of the heads-up information + */ +public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout { + private int mAbsoluteStartPadding; + private int mEndMargin; + private View mIconPlaceholder; + private TextView mTextView; + private NotificationData.Entry mShowingEntry; + private Rect mIconRect = new Rect(); + private int[] mTmpPosition = new int[2]; + private boolean mFirstLayout = true; + private boolean mPublicMode; + + public HeadsUpStatusBarView(Context context) { + this(context, null); + } + + public HeadsUpStatusBarView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + Resources res = getResources(); + mAbsoluteStartPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings) + + res.getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin_start); + mEndMargin = res.getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin_end); + setPaddingRelative(mAbsoluteStartPadding, 0, mEndMargin, 0); + } + + @VisibleForTesting + public HeadsUpStatusBarView(Context context, View iconPlaceholder, TextView textView) { + this(context); + mIconPlaceholder = iconPlaceholder; + mTextView = textView; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mIconPlaceholder = findViewById(R.id.icon_placeholder); + mTextView = findViewById(R.id.text); + } + + public void setEntry(NotificationData.Entry entry) { + if (entry != null) { + mShowingEntry = entry; + CharSequence text = entry.headsUpStatusBarText; + if (mPublicMode) { + text = entry.headsUpStatusBarTextPublic; + } + mTextView.setText(text); + } else { + mShowingEntry = null; + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + mIconPlaceholder.getLocationOnScreen(mTmpPosition); + int left = mTmpPosition[0]; + int top = mTmpPosition[1]; + int right = left + mIconPlaceholder.getWidth(); + int bottom = top + mIconPlaceholder.getHeight(); + mIconRect.set(left, top, right, bottom); + if (left != mAbsoluteStartPadding) { + int newPadding = mAbsoluteStartPadding - left + getPaddingStart(); + setPaddingRelative(newPadding, 0, mEndMargin, 0); + } + if (mFirstLayout) { + // we need to do the padding calculation in the first frame, so the layout specified + // our visibility to be INVISIBLE in the beginning. let's correct that and set it + // to GONE. + setVisibility(GONE); + mFirstLayout = false; + } + } + + public NotificationData.Entry getShowingEntry() { + return mShowingEntry; + } + + public Rect getIconDrawingRect() { + return mIconRect; + } + + public void onDarkChanged(Rect area, float darkIntensity, int tint) { + mTextView.setTextColor(DarkIconDispatcher.getTint(area, this, tint)); + } + + public void setPublicMode(boolean publicMode) { + mPublicMode = publicMode; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 73c87953cf45..f64b1bc2abe8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -602,14 +602,15 @@ public class NotificationContentView extends FrameLayout { && (mIsHeadsUp || mHeadsUpAnimatingAway) && !mContainingNotification.isOnKeyguard(); if (transitioningBetweenHunAndExpanded || pinned) { - return Math.min(mHeadsUpChild.getHeight(), mExpandedChild.getHeight()); + return Math.min(getViewHeight(VISIBLE_TYPE_HEADSUP), + getViewHeight(VISIBLE_TYPE_EXPANDED)); } } // Size change of the expanded version if ((mVisibleType == VISIBLE_TYPE_EXPANDED) && mContentHeightAtAnimationStart >= 0 && mExpandedChild != null) { - return Math.min(mContentHeightAtAnimationStart, mExpandedChild.getHeight()); + return Math.min(mContentHeightAtAnimationStart, getViewHeight(VISIBLE_TYPE_EXPANDED)); } int hint; @@ -619,16 +620,17 @@ public class NotificationContentView extends FrameLayout { VISIBLE_TYPE_AMBIENT_SINGLELINE)) { hint = mAmbientSingleLineChild.getHeight(); } else if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) { - hint = mHeadsUpChild.getHeight(); + hint = getViewHeight(VISIBLE_TYPE_HEADSUP); } else if (mExpandedChild != null) { - hint = mExpandedChild.getHeight(); + hint = getViewHeight(VISIBLE_TYPE_EXPANDED); } else { - hint = mContractedChild.getHeight() + mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.notification_action_list_height); + hint = getViewHeight(VISIBLE_TYPE_CONTRACTED) + + mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_action_list_height); } if (mExpandedChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_EXPANDED)) { - hint = Math.min(hint, mExpandedChild.getHeight()); + hint = Math.min(hint, getViewHeight(VISIBLE_TYPE_EXPANDED)); } return hint; } @@ -694,8 +696,8 @@ public class NotificationContentView extends FrameLayout { } private float calculateTransformationAmount() { - int startHeight = getViewForVisibleType(mTransformationStartVisibleType).getHeight(); - int endHeight = getViewForVisibleType(mVisibleType).getHeight(); + int startHeight = getViewHeight(mTransformationStartVisibleType); + int endHeight = getViewHeight(mVisibleType); int progress = Math.abs(mContentHeight - startHeight); int totalDistance = Math.abs(endHeight - startHeight); if (totalDistance == 0) { @@ -717,11 +719,23 @@ public class NotificationContentView extends FrameLayout { if (mContainingNotification.isShowingAmbient()) { return getShowingAmbientView().getHeight(); } else if (mExpandedChild != null) { - return mExpandedChild.getHeight() + getExtraRemoteInputHeight(mExpandedRemoteInput); + return getViewHeight(VISIBLE_TYPE_EXPANDED) + + getExtraRemoteInputHeight(mExpandedRemoteInput); } else if (mIsHeadsUp && mHeadsUpChild != null && !mContainingNotification.isOnKeyguard()) { - return mHeadsUpChild.getHeight() + getExtraRemoteInputHeight(mHeadsUpRemoteInput); + return getViewHeight(VISIBLE_TYPE_HEADSUP) + + getExtraRemoteInputHeight(mHeadsUpRemoteInput); } - return mContractedChild.getHeight(); + return getViewHeight(VISIBLE_TYPE_CONTRACTED); + } + + private int getViewHeight(int visibleType) { + View view = getViewForVisibleType(visibleType); + int height = view.getHeight(); + NotificationViewWrapper viewWrapper = getWrapperForView(view); + if (viewWrapper != null) { + height += viewWrapper.getHeaderTranslation(); + } + return height; } public int getMinHeight() { @@ -732,7 +746,7 @@ public class NotificationContentView extends FrameLayout { if (mContainingNotification.isShowingAmbient()) { return getShowingAmbientView().getHeight(); } else if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) { - return mContractedChild.getHeight(); + return getViewHeight(VISIBLE_TYPE_CONTRACTED); } else { return mSingleLineView.getHeight(); } @@ -1046,7 +1060,7 @@ public class NotificationContentView extends FrameLayout { private int getVisualTypeForHeight(float viewHeight) { boolean noExpandedChild = mExpandedChild == null; - if (!noExpandedChild && viewHeight == mExpandedChild.getHeight()) { + if (!noExpandedChild && viewHeight == getViewHeight(VISIBLE_TYPE_EXPANDED)) { return VISIBLE_TYPE_EXPANDED; } if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded()) { @@ -1055,13 +1069,13 @@ public class NotificationContentView extends FrameLayout { if ((mIsHeadsUp || mHeadsUpAnimatingAway) && mHeadsUpChild != null && !mContainingNotification.isOnKeyguard()) { - if (viewHeight <= mHeadsUpChild.getHeight() || noExpandedChild) { + if (viewHeight <= getViewHeight(VISIBLE_TYPE_HEADSUP) || noExpandedChild) { return VISIBLE_TYPE_HEADSUP; } else { return VISIBLE_TYPE_EXPANDED; } } else { - if (noExpandedChild || (viewHeight <= mContractedChild.getHeight() + if (noExpandedChild || (viewHeight <= getViewHeight(VISIBLE_TYPE_CONTRACTED) && (!mIsChildInGroup || isGroupExpanded() || !mContainingNotification.isExpanded(true /* allowOnKeyguard */)))) { return VISIBLE_TYPE_CONTRACTED; @@ -1616,19 +1630,19 @@ public class NotificationContentView extends FrameLayout { } public int getExpandHeight() { - View expandedChild = mExpandedChild; - if (expandedChild == null) { - expandedChild = mContractedChild; + int viewType = VISIBLE_TYPE_EXPANDED; + if (mExpandedChild == null) { + viewType = VISIBLE_TYPE_CONTRACTED; } - return expandedChild.getHeight() + getExtraRemoteInputHeight(mExpandedRemoteInput); + return getViewHeight(viewType) + getExtraRemoteInputHeight(mExpandedRemoteInput); } public int getHeadsUpHeight() { - View headsUpChild = mHeadsUpChild; - if (headsUpChild == null) { - headsUpChild = mContractedChild; + int viewType = VISIBLE_TYPE_HEADSUP; + if (mHeadsUpChild == null) { + viewType = VISIBLE_TYPE_CONTRACTED; } - return headsUpChild.getHeight()+ getExtraRemoteInputHeight(mHeadsUpRemoteInput); + return getViewHeight(viewType) + getExtraRemoteInputHeight(mHeadsUpRemoteInput); } public void setRemoteInputVisible(boolean remoteInputVisible) { @@ -1641,4 +1655,16 @@ public class NotificationContentView extends FrameLayout { clipChildren = clipChildren && !mRemoteInputVisible; super.setClipChildren(clipChildren); } + + public void setHeaderVisibleAmount(float headerVisibleAmount) { + if (mContractedWrapper != null) { + mContractedWrapper.setHeaderVisibleAmount(headerVisibleAmount); + } + if (mHeadsUpWrapper != null) { + mHeadsUpWrapper.setHeaderVisibleAmount(headerVisibleAmount); + } + if (mExpandedWrapper != null) { + mExpandedWrapper.setHeaderVisibleAmount(headerVisibleAmount); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 22a186ff1706..ebd22610b520 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -107,6 +107,8 @@ public class NotificationData { public CharSequence remoteInputTextWhenReset; public long lastRemoteInputSent = NOT_LAUNCHED_YET; public ArraySet<Integer> mActiveAppOps = new ArraySet<>(3); + public CharSequence headsUpStatusBarText; + public CharSequence headsUpStatusBarTextPublic; public Entry(StatusBarNotification n) { this.key = n.getKey(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 4f09133303de..abcdef3e7345 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -252,7 +252,8 @@ public class NotificationShelf extends ActivatableNotificationView implements } ExpandableNotificationRow row = (ExpandableNotificationRow) child; float notificationClipEnd; - boolean aboveShelf = ViewState.getFinalTranslationZ(row) > baseZHeight; + boolean aboveShelf = ViewState.getFinalTranslationZ(row) > baseZHeight + || row.isPinned(); boolean isLastChild = child == lastChild; float rowTranslationY = row.getTranslationY(); if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java index dfcd5e60719a..251e04b01afe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java @@ -52,6 +52,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { protected final ViewInvertHelper mInvertHelper; protected final ViewTransformationHelper mTransformationHelper; + private final int mTranslationForHeader; protected int mColor; private ImageView mIcon; @@ -63,6 +64,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { private boolean mIsLowPriority; private boolean mTransformLowPriorityTitle; private boolean mShowExpandButtonAtEnd; + protected float mHeaderTranslation; protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { super(ctx, view, row); @@ -99,6 +101,10 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { resolveHeaderViews(); updateInvertHelper(); addAppOpsOnClickListener(row); + mTranslationForHeader = ctx.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin) + - ctx.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin_top); } @Override @@ -243,6 +249,19 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } @Override + public void setHeaderVisibleAmount(float headerVisibleAmount) { + super.setHeaderVisibleAmount(headerVisibleAmount); + mNotificationHeader.setAlpha(headerVisibleAmount); + mHeaderTranslation = (1.0f - headerVisibleAmount) * mTranslationForHeader; + mView.setTranslationY(mHeaderTranslation); + } + + @Override + public int getHeaderTranslation() { + return (int) mHeaderTranslation; + } + + @Override public NotificationHeaderView getNotificationHeader() { return mNotificationHeader; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java index f9671182ab8c..f5110a2dbc2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java @@ -179,6 +179,9 @@ public class NotificationInflater { : builder.makeAmbientNotification(); } result.packageContext = packageContext; + result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */); + result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText( + true /* showingPublic */); return result; } @@ -456,6 +459,8 @@ public class NotificationInflater { } entry.cachedAmbientContentView = result.newAmbientView; } + entry.headsUpStatusBarText = result.headsUpStatusBarText; + entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic; if (endListener != null) { endListener.onAsyncInflationFinished(row.getEntry()); } @@ -665,6 +670,8 @@ public class NotificationInflater { private View inflatedExpandedView; private View inflatedAmbientView; private View inflatedPublicView; + private CharSequence headsUpStatusBarText; + private CharSequence headsUpStatusBarTextPublic; } @VisibleForTesting 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 d463eae6e43f..28beb21dba4b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java @@ -278,7 +278,10 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp 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()); + + // We also need to compensate for any header translation, since we're always at the end. + mActionsContainer.setTranslationY(constrainedContentHeight - mView.getHeight() + - getHeaderTranslation()); } } } 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 17eb4c110031..873f088f8431 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java @@ -133,6 +133,10 @@ public abstract class NotificationViewWrapper implements TransformableView { return null; } + public int getHeaderTranslation() { + return 0; + } + @Override public TransformState getCurrentState(int fadingView) { return null; @@ -198,4 +202,7 @@ public abstract class NotificationViewWrapper implements TransformableView { public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) { return false; } + + public void setHeaderVisibleAmount(float headerVisibleAmount) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java new file mode 100644 index 000000000000..cd97c05ede25 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -0,0 +1,185 @@ +/* + * 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.statusbar.phone; + +import android.app.Notification; +import android.app.PendingIntent; +import android.graphics.Rect; +import android.service.notification.StatusBarNotification; +import android.util.EventLog; +import android.util.Log; +import android.view.View; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.HeadsUpStatusBarView; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.policy.DarkIconDispatcher; +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; +import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; + +import java.util.stream.Stream; + +/** + * Controls the appearance of heads up notifications in the icon area and the header itself. + */ +class HeadsUpAppearanceController implements OnHeadsUpChangedListener, + DarkIconDispatcher.DarkReceiver { + private final NotificationIconAreaController mNotificationIconAreaController; + private final HeadsUpManagerPhone mHeadsUpManager; + private final NotificationStackScrollLayout mStackScroller; + private final HeadsUpStatusBarView mHeadsUpStatusBarView; + private final View mClockView; + private final DarkIconDispatcher mDarkIconDispatcher; + private float mExpandedHeight; + private boolean mIsExpanded; + private float mExpandFraction; + private ExpandableNotificationRow mTrackedChild; + private boolean mShown; + + public HeadsUpAppearanceController( + NotificationIconAreaController notificationIconAreaController, + HeadsUpManagerPhone headsUpManager, + View statusbarView) { + this(notificationIconAreaController, headsUpManager, + statusbarView.findViewById(R.id.heads_up_status_bar_view), + statusbarView.findViewById(R.id.notification_stack_scroller), + statusbarView.findViewById(R.id.notification_panel), + statusbarView.findViewById(R.id.clock)); + } + + @VisibleForTesting + public HeadsUpAppearanceController( + NotificationIconAreaController notificationIconAreaController, + HeadsUpManagerPhone headsUpManager, + HeadsUpStatusBarView headsUpStatusBarView, + NotificationStackScrollLayout stackScroller, + NotificationPanelView panelView, + View clockView) { + mNotificationIconAreaController = notificationIconAreaController; + mHeadsUpManager = headsUpManager; + mHeadsUpManager.addListener(this); + mHeadsUpStatusBarView = headsUpStatusBarView; + mStackScroller = stackScroller; + panelView.addTrackingHeadsUpListener(this::setTrackingHeadsUp); + mStackScroller.addOnExpandedHeightListener(this::setExpandedHeight); + mClockView = clockView; + mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class); + mDarkIconDispatcher.addDarkReceiver(this); + } + + @Override + public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { + updateTopEntry(); + updateHeader(headsUp.getEntry()); + } + + private void updateTopEntry() { + NotificationData.Entry newEntry = null; + if (!mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp()) { + newEntry = mHeadsUpManager.getTopEntry(); + } + NotificationData.Entry previousEntry = mHeadsUpStatusBarView.getShowingEntry(); + mHeadsUpStatusBarView.setEntry(newEntry); + if (newEntry != previousEntry) { + if (newEntry == null) { + // no heads up anymore, lets start the disappear animation + + setShown(false); + } else if (previousEntry == null) { + // We now have a headsUp and didn't have one before. Let's start the disappear + // animation + setShown(true); + } + mNotificationIconAreaController.showIconIsolated(newEntry == null ? null + : newEntry.icon, mHeadsUpStatusBarView.getIconDrawingRect()); + } + } + + private void setShown(boolean isShown) { + mShown = isShown; + mHeadsUpStatusBarView.setVisibility(isShown ? View.VISIBLE : View.GONE); + mClockView.setVisibility(!isShown ? View.VISIBLE : View.INVISIBLE); + } + + @VisibleForTesting + public boolean isShown() { + return mShown; + } + + @Override + public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) { + updateTopEntry(); + updateHeader(headsUp.getEntry()); + } + + public void setExpandedHeight(float expandedHeight, float appearFraction) { + boolean changedHeight = expandedHeight != mExpandedHeight; + mExpandedHeight = expandedHeight; + mExpandFraction = appearFraction; + boolean isExpanded = expandedHeight > 0; + if (changedHeight) { + updateHeadsUpHeaders(); + } + if (isExpanded != mIsExpanded) { + mIsExpanded = isExpanded; + updateTopEntry(); + } + } + + /** + * Set a headsUp to be tracked, meaning that it is currently being pulled down after being + * in a pinned state on the top. The expand animation is different in that case and we need + * to update the header constantly afterwards. + * + * @param trackedChild the tracked headsUp or null if it's not tracking anymore. + */ + public void setTrackingHeadsUp(ExpandableNotificationRow trackedChild) { + ExpandableNotificationRow previousTracked = mTrackedChild; + mTrackedChild = trackedChild; + if (previousTracked != null) { + updateHeader(previousTracked.getEntry()); + } + } + + private void updateHeadsUpHeaders() { + mHeadsUpManager.getAllEntries().forEach(entry -> { + updateHeader(entry); + }); + } + + private void updateHeader(NotificationData.Entry entry) { + ExpandableNotificationRow row = entry.row; + float headerVisibleAmount = 1.0f; + if (row.isPinned() || row == mTrackedChild) { + headerVisibleAmount = mExpandFraction; + } + row.setHeaderVisibleAmount(headerVisibleAmount); + } + + @Override + public void onDarkChanged(Rect area, float darkIntensity, int tint) { + mHeadsUpStatusBarView.onDarkChanged(area, darkIntensity, tint); + } + + public void setPublicMode(boolean publicMode) { + mHeadsUpStatusBarView.setPublicMode(publicMode); + updateTopEntry(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index d2cdc27d982c..fa0a774c249e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -29,6 +29,7 @@ import android.view.ViewTreeObserver; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; +import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.StatusBarState; @@ -52,12 +53,13 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, private static final boolean DEBUG = false; private final View mStatusBarWindowView; - private int mStatusBarHeight; private final NotificationGroupManager mGroupManager; private final StatusBar mBar; private final VisualStabilityManager mVisualStabilityManager; - private boolean mReleaseOnExpandFinish; + + private int mStatusBarHeight; + private int mHeadsUpInset; private boolean mTrackingHeadsUp; private HashSet<String> mSwipedOutKeys = new HashSet<>(); private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>(); @@ -101,9 +103,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, mBar = bar; mVisualStabilityManager = visualStabilityManager; - Resources resources = mContext.getResources(); - mStatusBarHeight = resources.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height); + initResources(); addListener(new OnHeadsUpChangedListener() { @Override @@ -114,6 +114,20 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, }); } + private void initResources() { + Resources resources = mContext.getResources(); + mStatusBarHeight = resources.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + mHeadsUpInset = mStatusBarHeight + resources.getDimensionPixelSize( + R.dimen.heads_up_status_bar_padding); + } + + @Override + public void onDensityOrFontScaleChanged() { + super.onDensityOrFontScaleChanged(); + initResources(); + } + /////////////////////////////////////////////////////////////////////////////////////////////// // Public methods: @@ -283,10 +297,10 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, topEntry.getLocationOnScreen(mTmpTwoArray); int minX = mTmpTwoArray[0]; int maxX = mTmpTwoArray[0] + topEntry.getWidth(); - int maxY = topEntry.getIntrinsicHeight(); + int height = topEntry.getIntrinsicHeight(); info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(minX, 0, maxX, maxY); + info.touchableRegion.set(minX, 0, maxX, mHeadsUpInset + height); } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) { info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java index 2bfdefe39017..903b81339869 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -23,7 +23,6 @@ import android.view.ViewConfiguration; import com.android.systemui.Gefingerpoken; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; /** @@ -102,10 +101,11 @@ public class HeadsUpTouchHelper implements Gefingerpoken { mCollapseSnoozes = h < 0; mInitialTouchX = x; mInitialTouchY = y; - int expandedHeight = mPickedChild.getActualHeight(); - mPanel.setPanelScrimMinFraction((float) expandedHeight + int startHeight = (int) (mPickedChild.getActualHeight() + + mPickedChild.getTranslationY()); + mPanel.setPanelScrimMinFraction((float) startHeight / mPanel.getMaxPanelHeight()); - mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight); + mPanel.startExpandMotion(x, y, true /* startTracking */, startHeight); mPanel.startExpandingFromPeek(); // This call needs to be after the expansion start otherwise we will get a // flicker of one frame as it's not expanded yet. @@ -135,7 +135,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken { private void setTrackingHeadsUp(boolean tracking) { mTrackingHeadsUp = tracking; mHeadsUpManager.setTrackingHeadsUp(tracking); - mPanel.setTrackingHeadsUp(tracking); + mPanel.setTrackedHeadsUp(tracking ? mPickedChild : null); } public void notifyFling(boolean collapse) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index e1aed7a04c35..51adebfd05c7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -296,4 +296,8 @@ public class NotificationIconAreaController implements DarkReceiver { mNotificationIcons.setDark(dark, false, 0); mShelfIcons.setDark(dark, false, 0); } + + public void showIconIsolated(StatusBarIconView icon, Rect absoluteIconPosition) { + mNotificationIcons.showIconIsolated(icon, absoluteIconPosition); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index b29ac9067556..53ae0f82b0ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -21,12 +21,9 @@ import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.Rect; import android.graphics.drawable.Icon; -import android.os.AsyncTask; -import android.os.VibrationEffect; -import android.os.Vibrator; import android.support.v4.util.ArrayMap; -import android.support.v4.util.ArraySet; import android.util.AttributeSet; import android.view.View; @@ -127,6 +124,9 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { private float mVisualOverflowStart; // Keep track of overflow in range [0, 3] private int mNumDots; + private StatusBarIconView mIsolatedIcon; + private Rect mIsolatedIconLocation; + private int[] mAbsolutePosition = new int[2]; public NotificationIconContainer(Context context, AttributeSet attrs) { @@ -196,13 +196,18 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mIconSize = child.getWidth(); } } + getLocationOnScreen(mAbsolutePosition); if (mIsStaticLayout) { - resetViewStates(); - calculateIconTranslations(); - applyIconStates(); + updateState(); } } + private void updateState() { + resetViewStates(); + calculateIconTranslations(); + applyIconStates(); + } + public void applyIconStates() { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); @@ -306,7 +311,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { View view = getChildAt(i); ViewState iconState = mIconStates.get(view); iconState.initFrom(view); - iconState.alpha = 1.0f; + iconState.alpha = mIsolatedIcon == null || view == mIsolatedIcon ? 1.0f : 0.0f; iconState.hidden = false; } } @@ -402,6 +407,16 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { iconState.xTranslation = getWidth() - iconState.xTranslation - view.getWidth(); } } + if (mIsolatedIcon != null) { + IconState iconState = mIconStates.get(mIsolatedIcon); + if (iconState != null) { + // Most of the time the icon isn't yet added when this is called but only happening + // later + iconState.xTranslation = mIsolatedIconLocation.left - mAbsolutePosition[0] + - (1 - mIsolatedIcon.getIconScale()) * mIsolatedIcon.getWidth() / 2.0f; + iconState.visibleState = StatusBarIconView.STATE_ICON; + } + } } private float getLayoutEnd() { @@ -573,6 +588,12 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mReplacingIcons = replacingIcons; } + public void showIconIsolated(StatusBarIconView icon, Rect absoluteIconPosition) { + mIsolatedIcon = icon; + mIsolatedIconLocation = absoluteIconPosition; + updateState(); + } + public class IconState extends ViewState { public static final int NO_VALUE = NotificationIconContainer.NO_VALUE; public float iconAppearAmount = 1.0f; 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 2711d7ac7a89..63ee9ff684cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -74,7 +74,9 @@ import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.StackStateAnimator; +import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; public class NotificationPanelView extends PanelView implements ExpandableView.OnHeightChangedListener, @@ -243,6 +245,8 @@ public class NotificationPanelView extends PanelView implements private float mExpandOffset; private boolean mHideIconsDuringNotificationLaunch = true; private int mStackScrollerMeasuringPass; + private ArrayList<Consumer<ExpandableNotificationRow>> mTrackingHeadsUpListeners + = new ArrayList<>(); public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); @@ -269,6 +273,7 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.setOnHeightChangedListener(this); mNotificationStackScroller.setOverscrollTopChangedListener(this); mNotificationStackScroller.setOnEmptySpaceClickListener(this); + addTrackingHeadsUpListener(mNotificationStackScroller::setTrackingHeadsUp); mKeyguardBottomArea = findViewById(R.id.keyguard_bottom_area); mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim); mLastOrientation = getResources().getConfiguration().orientation; @@ -1780,11 +1785,19 @@ public class NotificationPanelView extends PanelView implements mQsExpandImmediate = false; mTwoFingerQsExpandPossible = false; mIsExpansionFromHeadsUp = false; - mNotificationStackScroller.setTrackingHeadsUp(false); + notifyListenersTrackingHeadsUp(null); mExpandingFromHeadsUp = false; setPanelScrimMinFraction(0.0f); } + private void notifyListenersTrackingHeadsUp(ExpandableNotificationRow pickedChild) { + for (int i = 0; i < mTrackingHeadsUpListeners.size(); i++) { + Consumer<ExpandableNotificationRow> listener + = mTrackingHeadsUpListeners.get(i); + listener.accept(pickedChild); + } + } + private void setListening(boolean listening) { mKeyguardStatusBar.setListening(listening); if (mQs == null) return; @@ -2351,9 +2364,9 @@ public class NotificationPanelView extends PanelView implements this); } - public void setTrackingHeadsUp(boolean tracking) { - if (tracking) { - mNotificationStackScroller.setTrackingHeadsUp(true); + public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) { + if (pickedChild != null) { + notifyListenersTrackingHeadsUp(pickedChild); mExpandingFromHeadsUp = true; } // otherwise we update the state when the expansion is finished @@ -2696,4 +2709,8 @@ public class NotificationPanelView extends PanelView implements } } } + + public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) { + mTrackingHeadsUpListeners.add(listener); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 7422a4343cb4..4382e276d60c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -189,6 +189,7 @@ import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.GestureRecorder; +import com.android.systemui.statusbar.HeadsUpStatusBarView; import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationData; @@ -602,6 +603,7 @@ public class StatusBar extends SystemUI implements DemoMode, private NavigationBarFragment mNavigationBar; private View mNavigationBarView; protected ActivityLaunchAnimator mActivityLaunchAnimator; + private HeadsUpAppearanceController mHeadsUpAppearanceController; @Override public void start() { @@ -807,6 +809,8 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarView.setPanel(mNotificationPanel); mStatusBarView.setScrimController(mScrimController); mStatusBarView.setBouncerShowing(mBouncerShowing); + mHeadsUpAppearanceController = new HeadsUpAppearanceController( + mNotificationIconAreaController, mHeadsUpManager, mStatusBarWindow); setAreThereNotifications(); checkBarModes(); }).getFragmentManager() @@ -1087,6 +1091,7 @@ public class StatusBar extends SystemUI implements DemoMode, mKeyguardUserSwitcher.onDensityOrFontScaleChanged(); } mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext); + mHeadsUpManager.onDensityOrFontScaleChanged(); reevaluateStyles(); } @@ -1991,7 +1996,7 @@ public class StatusBar extends SystemUI implements DemoMode, mVisualStabilityManager.setPanelExpanded(isExpanded); if (isExpanded && getBarState() != StatusBarState.KEYGUARD) { if (DEBUG) { - Log.v(TAG, "clearing notification effects from setPanelExpanded"); + Log.v(TAG, "clearing notification effects from setExpandedHeight"); } clearNotificationEffects(); } @@ -2813,7 +2818,7 @@ public class StatusBar extends SystemUI implements DemoMode, public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) { mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); - entry.row.updateMaxHeights(); + entry.row.notifyHeightChanged(true /* needsAnimation */); } public void lockScrollTo(NotificationData.Entry entry) { mStackScroller.lockScrollTo(entry.row); @@ -3833,6 +3838,9 @@ public class StatusBar extends SystemUI implements DemoMode, if (mStackScroller == null) return; boolean onKeyguard = mState == StatusBarState.KEYGUARD; boolean publicMode = mLockscreenUserManager.isAnyProfilePublicMode(); + if (mHeadsUpAppearanceController != null) { + mHeadsUpAppearanceController.setPublicMode(publicMode); + } mStackScroller.setHideSensitive(publicMode, goingToFullShade); mStackScroller.setDimmed(onKeyguard, fromShadeLocked /* animate */); mStackScroller.setExpandingEnabled(!onKeyguard); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index 040d7ec32ecc..aeda55a8bf8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -443,6 +443,9 @@ public class HeadsUpManager { entry.reset(); } + public void onDensityOrFontScaleChanged() { + } + /** * This represents a notification and how long it is in a heads up mode. It also manages its * lifecycle automatically when created. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java index ac2a1e1e041f..e5ab712e9bdd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java @@ -102,6 +102,9 @@ public class NotificationChildrenContainer extends ViewGroup { private boolean mShowDividersWhenExpanded; private boolean mHideDividersDuringExpand; + private int mTranslationForHeader; + private int mCurrentHeaderTranslation = 0; + private float mHeaderVisibleAmount = 1.0f; public NotificationChildrenContainer(Context context) { this(context, null); @@ -142,6 +145,9 @@ public class NotificationChildrenContainer extends ViewGroup { res.getBoolean(R.bool.config_showDividersWhenGroupNotificationExpanded); mHideDividersDuringExpand = res.getBoolean(R.bool.config_hideDividersDuringExpand); + mTranslationForHeader = res.getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin) + - mNotificationHeaderMargin; } @Override @@ -486,7 +492,7 @@ public class NotificationChildrenContainer extends ViewGroup { if (showingAsLowPriority()) { return mNotificationHeaderLowPriority.getHeight(); } - int intrinsicHeight = mNotificationHeaderMargin; + int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation; int visibleChildren = 0; int childCount = mChildren.size(); boolean firstChild = true; @@ -541,7 +547,7 @@ public class NotificationChildrenContainer extends ViewGroup { public void getState(StackScrollState resultState, ExpandableViewState parentState, AmbientState ambientState) { int childCount = mChildren.size(); - int yPosition = mNotificationHeaderMargin; + int yPosition = mNotificationHeaderMargin + mCurrentHeaderTranslation; boolean firstChild = true; int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(); int lastVisibleIndex = maxAllowedVisibleChildren - 1; @@ -645,6 +651,11 @@ public class NotificationChildrenContainer extends ViewGroup { mHeaderViewState.zTranslation = childrenExpandedAndNotAnimating ? parentState.zTranslation : 0; + mHeaderViewState.yTranslation = mCurrentHeaderTranslation; + mHeaderViewState.alpha = mHeaderVisibleAmount; + // The hiding is done automatically by the alpha, otherwise we'll pick it up again + // in the next frame with the initFrom call above and have an invisible header + mHeaderViewState.hidden = false; } } @@ -1009,7 +1020,8 @@ public class NotificationChildrenContainer extends ViewGroup { return getMinHeight(NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED, true /* likeHighPriority */); } - int maxContentHeight = mNotificationHeaderMargin + mNotificatonTopPadding; + int maxContentHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation + + mNotificatonTopPadding; int visibleChildren = 0; int childCount = mChildren.size(); for (int i = 0; i < childCount; i++) { @@ -1071,7 +1083,8 @@ public class NotificationChildrenContainer extends ViewGroup { } private int getVisibleChildrenExpandHeight() { - int intrinsicHeight = mNotificationHeaderMargin + mNotificatonTopPadding + mDividerHeight; + int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation + + mNotificatonTopPadding + mDividerHeight; int visibleChildren = 0; int childCount = mChildren.size(); int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */); @@ -1110,7 +1123,7 @@ public class NotificationChildrenContainer extends ViewGroup { if (!likeHighPriority && showingAsLowPriority()) { return mNotificationHeaderLowPriority.getHeight(); } - int minExpandHeight = mNotificationHeaderMargin; + int minExpandHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation; int visibleChildren = 0; boolean firstChild = true; int childCount = mChildren.size(); @@ -1190,7 +1203,8 @@ public class NotificationChildrenContainer extends ViewGroup { } public int getPositionInLinearLayout(View childInGroup) { - int position = mNotificationHeaderMargin + mNotificatonTopPadding; + int position = mNotificationHeaderMargin + mCurrentHeaderTranslation + + mNotificatonTopPadding; for (int i = 0; i < mChildren.size(); i++) { ExpandableNotificationRow child = mChildren.get(i); @@ -1281,4 +1295,9 @@ public class NotificationChildrenContainer extends ViewGroup { last = false; } } + + public void setHeaderVisibleAmount(float headerVisibleAmount) { + mHeaderVisibleAmount = headerVisibleAmount; + mCurrentHeaderTranslation = (int) ((1.0f - headerVisibleAmount) * mTranslationForHeader); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationRoundnessManager.java index 30997717f318..a36c966a3310 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationRoundnessManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationRoundnessManager.java @@ -20,7 +20,6 @@ import android.view.View; import com.android.systemui.statusbar.ActivatableNotificationView; import com.android.systemui.statusbar.ExpandableNotificationRow; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import java.util.HashSet; @@ -35,6 +34,8 @@ class NotificationRoundnessManager implements OnHeadsUpChangedListener { private ActivatableNotificationView mLast; private HashSet<View> mAnimatedChildren; private Runnable mRoundingChangedCallback; + private ExpandableNotificationRow mTrackedHeadsUp; + private float mAppearFraction; @Override public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { @@ -66,11 +67,20 @@ class NotificationRoundnessManager implements OnHeadsUpChangedListener { if (view == mLast && !top) { return 1.0f; } + if (view == mTrackedHeadsUp && mAppearFraction <= 0.0f) { + // If we're pushing up on a headsup the appear fraction is < 0 and it needs to still be + // rounded. + return 1.0f; + } return 0.0f; } - public void setExpanded(boolean expanded) { - mExpanded = expanded; + public void setExpanded(float expandedHeight, float appearFraction) { + mExpanded = expandedHeight != 0.0f; + mAppearFraction = appearFraction; + if (mTrackedHeadsUp != null) { + updateRounding(mTrackedHeadsUp, true); + } } public void setFirstAndLastBackgroundChild(ActivatableNotificationView first, @@ -106,4 +116,8 @@ class NotificationRoundnessManager implements OnHeadsUpChangedListener { public void setOnRoundingChangedCallback(Runnable roundingChangedCallback) { mRoundingChangedCallback = roundingChangedCallback; } + + public void setTrackingHeadsUp(ExpandableNotificationRow row) { + mTrackedHeadsUp = row; + } } 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 e4551674116f..174e73476695 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -94,8 +94,8 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; @@ -108,6 +108,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.function.BiConsumer; /** * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. @@ -404,6 +405,8 @@ public class NotificationStackScrollLayout extends ViewGroup private final Rect mTmpRect = new Rect(); private int mClockBottom; private int mAntiBurnInOffsetX; + private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>(); + private int mHeadsUpInset; public NotificationStackScrollLayout(Context context) { this(context, null); @@ -443,6 +446,7 @@ public class NotificationStackScrollLayout extends ViewGroup mDarkSeparatorPadding = res.getDimensionPixelSize(R.dimen.widget_bottom_separator_padding); mRoundnessManager.setAnimatedChildren(mChildrenToAddAnimated); mRoundnessManager.setOnRoundingChangedCallback(this::invalidate); + addOnExpandedHeightListener(mRoundnessManager::setExpanded); updateWillNotDraw(); mBackgroundPaint.setAntiAlias(true); @@ -585,13 +589,15 @@ public class NotificationStackScrollLayout extends ViewGroup res.getDimensionPixelSize(R.dimen.notification_divider_height_increased); mMinTopOverScrollToEscape = res.getDimensionPixelSize( R.dimen.min_top_overscroll_to_qs); - mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height); + mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height); mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom); mSidePaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings); mMinInteractionHeight = res.getDimensionPixelSize( R.dimen.notification_min_interaction_height); mCornerRadius = res.getDimensionPixelSize( Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); + mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize( + R.dimen.heads_up_status_bar_padding); } public void setDrawBackgroundAsSrc(boolean asSrc) { @@ -704,7 +710,8 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateAlgorithmLayoutMinHeight() { - mAmbientState.setLayoutMinHeight(mQsExpanded && !onKeyguard() ? getLayoutMinHeight() : 0); + mAmbientState.setLayoutMinHeight(mQsExpanded && !onKeyguard() || isHeadsUpTransition() + ? getLayoutMinHeight() : 0); } /** @@ -857,11 +864,12 @@ public class NotificationStackScrollLayout extends ViewGroup float translationY; float appearEndPosition = getAppearEndPosition(); float appearStartPosition = getAppearStartPosition(); + float appearFraction = 1.0f; if (height >= appearEndPosition) { translationY = 0; stackHeight = (int) height; } else { - float appearFraction = getAppearFraction(height); + appearFraction = getAppearFraction(height); if (appearFraction >= 0) { translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0, appearFraction); @@ -870,7 +878,12 @@ public class NotificationStackScrollLayout extends ViewGroup // start translationY = height - appearStartPosition + getExpandTranslationStart(); } - stackHeight = (int) (height - translationY); + if (isHeadsUpTransition()) { + stackHeight = mFirstVisibleBackgroundChild.getPinnedHeadsUpHeight(); + translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction); + } else { + stackHeight = (int) (height - translationY); + } } if (stackHeight != mCurrentStackHeight) { mCurrentStackHeight = stackHeight; @@ -878,6 +891,10 @@ public class NotificationStackScrollLayout extends ViewGroup requestChildrenUpdate(); } setStackTranslation(translationY); + for (int i = 0; i < mExpandedHeightListeners.size(); i++) { + BiConsumer<Float, Float> listener = mExpandedHeightListeners.get(i); + listener.accept(mExpandedHeight, appearFraction); + } } private void setRequestedClipBounds(Rect clipRect) { @@ -912,12 +929,8 @@ public class NotificationStackScrollLayout extends ViewGroup * Measured in absolute height. */ private float getAppearStartPosition() { - if (mTrackingHeadsUp && mFirstVisibleBackgroundChild != null) { - if (mAmbientState.isAboveShelf(mFirstVisibleBackgroundChild)) { - // If we ever expanded beyond the first notification, it's allowed to merge into - // the shelf - return mFirstVisibleBackgroundChild.getPinnedHeadsUpHeight(); - } + if (isHeadsUpTransition()) { + return mHeadsUpInset + mFirstVisibleBackgroundChild.getPinnedHeadsUpHeight(); } return getMinExpansionHeight(); } @@ -951,17 +964,14 @@ public class NotificationStackScrollLayout extends ViewGroup int appearPosition; int notGoneChildCount = getNotGoneChildCount(); if (mEmptyShadeView.getVisibility() == GONE && notGoneChildCount != 0) { - int minNotificationsForShelf = 1; - if (mTrackingHeadsUp + if (isHeadsUpTransition() || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) { appearPosition = getTopHeadsUpPinnedHeight(); - minNotificationsForShelf = 2; } else { appearPosition = 0; - } - if (notGoneChildCount >= minNotificationsForShelf - && mShelf.getVisibility() != GONE) { - appearPosition += mShelf.getIntrinsicHeight(); + if (notGoneChildCount >= 1 && mShelf.getVisibility() != GONE) { + appearPosition += mShelf.getIntrinsicHeight(); + } } } else { appearPosition = mEmptyShadeView.getHeight(); @@ -969,6 +979,11 @@ public class NotificationStackScrollLayout extends ViewGroup return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding); } + private boolean isHeadsUpTransition() { + return mTrackingHeadsUp && mFirstVisibleBackgroundChild != null + && mAmbientState.isAboveShelf(mFirstVisibleBackgroundChild); + } + /** * @param height the height of the panel * @return the fraction of the appear animation that has been performed @@ -2524,6 +2539,9 @@ public class NotificationStackScrollLayout extends ViewGroup } public int getLayoutMinHeight() { + if (isHeadsUpTransition()) { + return getTopHeadsUpPinnedHeight(); + } return mShelf.getVisibility() == GONE ? 0 : mShelf.getIntrinsicHeight(); } @@ -3551,7 +3569,6 @@ public class NotificationStackScrollLayout extends ViewGroup private void setIsExpanded(boolean isExpanded) { boolean changed = isExpanded != mIsExpanded; mIsExpanded = isExpanded; - mRoundnessManager.setExpanded(isExpanded); mStackScrollAlgorithm.setIsExpanded(isExpanded); if (changed) { if (!mIsExpanded) { @@ -4300,8 +4317,9 @@ public class NotificationStackScrollLayout extends ViewGroup requestChildrenUpdate(); } - public void setTrackingHeadsUp(boolean trackingHeadsUp) { - mTrackingHeadsUp = trackingHeadsUp; + public void setTrackingHeadsUp(ExpandableNotificationRow row) { + mTrackingHeadsUp = row != null; + mRoundnessManager.setTrackingHeadsUp(row); } public void setScrimController(ScrimController scrimController) { @@ -4484,6 +4502,16 @@ public class NotificationStackScrollLayout extends ViewGroup } /** + * Add a listener whenever the expanded height changes. The first value passed as an argument + * is the expanded height and the second one is the appearFraction. + * + * @param listener the listener to notify. + */ + public void addOnExpandedHeightListener(BiConsumer<Float, Float> listener) { + mExpandedHeightListeners.add(listener); + } + + /** * A listener that is notified when the empty space below the notifications is clicked on */ public interface OnEmptySpaceClickListener { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index 51737a863748..bf6af22a0cca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -50,6 +50,7 @@ public class StackScrollAlgorithm { private boolean mIsExpanded; private boolean mClipNotificationScrollToTop; private int mStatusBarHeight; + private float mHeadsUpInset; public StackScrollAlgorithm(Context context) { initView(context); @@ -68,6 +69,8 @@ public class StackScrollAlgorithm { mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height); mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height); mClipNotificationScrollToTop = res.getBoolean(R.bool.config_clipNotificationScrollToTop); + mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize( + R.dimen.heads_up_status_bar_padding); } public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) { @@ -457,7 +460,7 @@ public class StackScrollAlgorithm { } } if (row.isPinned()) { - childState.yTranslation = Math.max(childState.yTranslation, 0); + childState.yTranslation = Math.max(childState.yTranslation, mHeadsUpInset); childState.height = Math.max(row.getIntrinsicHeight(), childState.height); childState.hidden = false; ExpandableViewState topState = resultState.getViewStateForView(topHeadsUpEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java new file mode 100644 index 000000000000..c904ef30dd16 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -0,0 +1,113 @@ +/* + * 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.statusbar.phone; + +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.View; +import android.widget.TextView; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.TestableDependency; +import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.HeadsUpStatusBarView; +import com.android.systemui.statusbar.NotificationTestHelper; +import com.android.systemui.statusbar.policy.DarkIconDispatcher; +import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashSet; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class HeadsUpAppearanceControllerTest extends SysuiTestCase { + + private HeadsUpAppearanceController mHeadsUpAppearanceController; + private ExpandableNotificationRow mFirst; + private HeadsUpStatusBarView mHeadsUpStatusBarView; + private HeadsUpManagerPhone mHeadsUpManager; + + @Before + public void setUp() throws Exception { + NotificationTestHelper testHelper = new NotificationTestHelper(getContext()); + mFirst = testHelper.createRow(); + mDependency.injectMockDependency(DarkIconDispatcher.class); + mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class), + mock(TextView.class)); + mHeadsUpManager = mock(HeadsUpManagerPhone.class); + mHeadsUpAppearanceController = new HeadsUpAppearanceController( + mock(NotificationIconAreaController.class), + mHeadsUpManager, + mHeadsUpStatusBarView, + mock(NotificationStackScrollLayout.class), + mock(NotificationPanelView.class), + new View(mContext)); + mHeadsUpAppearanceController.setExpandedHeight(0.0f, 0.0f); + } + + @Test + public void testShowinEntryUpdated() { + mFirst.setPinned(true); + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); + when(mHeadsUpManager.getTopEntry()).thenReturn(mFirst.getEntry()); + mHeadsUpAppearanceController.onHeadsUpPinned(mFirst); + Assert.assertEquals(mFirst.getEntry(), mHeadsUpStatusBarView.getShowingEntry()); + + mFirst.setPinned(false); + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); + mHeadsUpAppearanceController.onHeadsUpUnPinned(mFirst); + Assert.assertEquals(null, mHeadsUpStatusBarView.getShowingEntry()); + } + + @Test + public void testShownUpdated() { + mFirst.setPinned(true); + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); + when(mHeadsUpManager.getTopEntry()).thenReturn(mFirst.getEntry()); + mHeadsUpAppearanceController.onHeadsUpPinned(mFirst); + Assert.assertTrue(mHeadsUpAppearanceController.isShown()); + + mFirst.setPinned(false); + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); + mHeadsUpAppearanceController.onHeadsUpUnPinned(mFirst); + Assert.assertFalse(mHeadsUpAppearanceController.isShown()); + } + + @Test + public void testHeaderUpdated() { + mFirst.setPinned(true); + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); + when(mHeadsUpManager.getTopEntry()).thenReturn(mFirst.getEntry()); + mHeadsUpAppearanceController.onHeadsUpPinned(mFirst); + Assert.assertEquals(mFirst.getHeaderVisibleAmount(), 0.0f, 0.0f); + + mFirst.setPinned(false); + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); + mHeadsUpAppearanceController.onHeadsUpUnPinned(mFirst); + Assert.assertEquals(mFirst.getHeaderVisibleAmount(), 1.0f, 0.0f); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationRoundnessManagerTest.java index ea754c71cad6..1d2c01dbd564 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationRoundnessManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationRoundnessManagerTest.java @@ -65,7 +65,7 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { mRoundnessManager.setOnRoundingChangedCallback(mRoundnessCallback); mRoundnessManager.setAnimatedChildren(mAnimatedChildren); mRoundnessManager.setFirstAndLastBackgroundChild(mFirst, mFirst); - mRoundnessManager.setExpanded(true); + mRoundnessManager.setExpanded(1.0f, 1.0f); } @Test @@ -112,7 +112,7 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { @Test public void testRoundedWhenPinnedAndCollapsed() { mFirst.setPinned(true); - mRoundnessManager.setExpanded(false); + mRoundnessManager.setExpanded(0.0f /* expandedHeight */, 0.0f /* appearFraction */); mRoundnessManager.setFirstAndLastBackgroundChild(mSecond, mSecond); Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); @@ -121,7 +121,7 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { @Test public void testRoundedWhenGoingAwayAndCollapsed() { mFirst.setHeadsUpAnimatingAway(true); - mRoundnessManager.setExpanded(false); + mRoundnessManager.setExpanded(0.0f /* expandedHeight */, 0.0f /* appearFraction */); mRoundnessManager.setFirstAndLastBackgroundChild(mSecond, mSecond); Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); @@ -130,7 +130,25 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { @Test public void testRoundedNormalRoundingWhenExpanded() { mFirst.setHeadsUpAnimatingAway(true); - mRoundnessManager.setExpanded(true); + mRoundnessManager.setExpanded(1.0f /* expandedHeight */, 0.0f /* appearFraction */); + mRoundnessManager.setFirstAndLastBackgroundChild(mSecond, mSecond); + Assert.assertEquals(0.0f, mFirst.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(0.0f, mFirst.getCurrentTopRoundness(), 0.0f); + } + + @Test + public void testTrackingHeadsUpRoundedIfPushingUp() { + mRoundnessManager.setExpanded(1.0f /* expandedHeight */, -0.5f /* appearFraction */); + mRoundnessManager.setTrackingHeadsUp(mFirst); + mRoundnessManager.setFirstAndLastBackgroundChild(mSecond, mSecond); + Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); + } + + @Test + public void testTrackingHeadsUpNotRoundedIfPushingDown() { + mRoundnessManager.setExpanded(1.0f /* expandedHeight */, 0.5f /* appearFraction */); + mRoundnessManager.setTrackingHeadsUp(mFirst); mRoundnessManager.setFirstAndLastBackgroundChild(mSecond, mSecond); Assert.assertEquals(0.0f, mFirst.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(0.0f, mFirst.getCurrentTopRoundness(), 0.0f); |