diff options
author | Selim Cinek <cinek@google.com> | 2015-03-13 21:50:26 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-03-13 21:50:38 +0000 |
commit | ed755abbb0205cd441068943359fd17fe528d409 (patch) | |
tree | 469f734b39ad00532163d819be875a06c3ce939b /packages/SystemUI | |
parent | 920752e98af46e272f1b592e5aed690905e0495a (diff) | |
parent | b5605e58cb8080c8c887b1885336b707596c8094 (diff) | |
download | frameworks_base-ed755abbb0205cd441068943359fd17fe528d409.zip frameworks_base-ed755abbb0205cd441068943359fd17fe528d409.tar.gz frameworks_base-ed755abbb0205cd441068943359fd17fe528d409.tar.bz2 |
Merge "Enable surfacing of notification children"
Diffstat (limited to 'packages/SystemUI')
31 files changed, 1500 insertions, 80 deletions
diff --git a/packages/SystemUI/res/drawable/ic_collapse_children.xml b/packages/SystemUI/res/drawable/ic_collapse_children.xml new file mode 100644 index 0000000..b0ce1e6 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_collapse_children.xml @@ -0,0 +1,19 @@ +<!-- + ~ 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 + --> + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:insetTop="2dp" + android:drawable="@drawable/ic_expand_less"/> diff --git a/packages/SystemUI/res/drawable/ic_expand_children.xml b/packages/SystemUI/res/drawable/ic_expand_children.xml new file mode 100644 index 0000000..1762be4 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_expand_children.xml @@ -0,0 +1,18 @@ +<!-- + ~ 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 + --> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:insetTop="2dp" + android:drawable="@drawable/ic_expand_more"/> diff --git a/packages/SystemUI/res/drawable/ic_expand_less.xml b/packages/SystemUI/res/drawable/ic_expand_less.xml new file mode 100644 index 0000000..e968013 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_expand_less.xml @@ -0,0 +1,24 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="20.0dp" + android:height="20.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M12.000000,8.000000l-6.000000,6.000000 1.400000,1.400000 4.600000,-4.599999 4.600000,4.599999 1.400000,-1.400000z" + android:fillColor="#FF000000"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_expand_more.xml b/packages/SystemUI/res/drawable/ic_expand_more.xml new file mode 100644 index 0000000..72e98ec --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_expand_more.xml @@ -0,0 +1,24 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="20.0dp" + android:height="20.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M16.600000,8.600000l-4.600000,4.599999 -4.600000,-4.599999 -1.400000,1.400000 6.000000,6.000000 6.000000,-6.000000z" + android:fillColor="#FF000000"/> +</vector> diff --git a/packages/SystemUI/res/layout/notification_children_container.xml b/packages/SystemUI/res/layout/notification_children_container.xml new file mode 100644 index 0000000..ac6a000 --- /dev/null +++ b/packages/SystemUI/res/layout/notification_children_container.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> + +<com.android.systemui.statusbar.stack.NotificationChildrenContainer + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> diff --git a/packages/SystemUI/res/layout/notification_children_divider.xml b/packages/SystemUI/res/layout/notification_children_divider.xml new file mode 100644 index 0000000..f011afe --- /dev/null +++ b/packages/SystemUI/res/layout/notification_children_divider.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> + +<com.android.systemui.statusbar.AlphaOptimizedView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/notification_more_divider" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_children_divider_height" + android:background="@*android:drawable/notification_template_divider" /> diff --git a/packages/SystemUI/res/layout/notification_collapse_button.xml b/packages/SystemUI/res/layout/notification_collapse_button.xml new file mode 100644 index 0000000..3ec5f63 --- /dev/null +++ b/packages/SystemUI/res/layout/notification_collapse_button.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_bottom_decor_height"> + <TextView + android:id="@+id/notification_expand_button" + android:layout_width="wrap_content" + android:layout_height="@dimen/notification_bottom_decor_height" + android:background="@null" + android:layout_gravity="top|center_horizontal" + android:gravity="center_vertical|center_horizontal" + android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title" + android:text="@string/notification_collapse_button_text" + android:drawableEnd="@drawable/ic_collapse_children" + android:drawablePadding="1dp" + /> +</FrameLayout> diff --git a/packages/SystemUI/res/layout/notification_expand_button.xml b/packages/SystemUI/res/layout/notification_expand_button.xml new file mode 100644 index 0000000..3c478f7 --- /dev/null +++ b/packages/SystemUI/res/layout/notification_expand_button.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_bottom_decor_height"> + <TextView + android:id="@+id/notification_expand_button" + android:layout_width="wrap_content" + android:layout_height="@dimen/notification_bottom_decor_height" + android:background="@null" + android:layout_gravity="top|center_horizontal" + android:gravity="center_vertical|center_horizontal" + android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title" + android:text="@string/notification_expand_button_text" + android:drawableEnd="@drawable/ic_expand_children" + android:drawablePadding="1dp" + /> + <com.android.systemui.statusbar.AlphaOptimizedView + android:id="@+id/notification_expand_divider" + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_gravity="top|center" + android:background="@*android:drawable/notification_template_divider" /> +</FrameLayout> diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml index e9d86d6..ea7ce96 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_row.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml @@ -53,6 +53,22 @@ /> <ViewStub + android:layout="@layout/notification_children_container" + android:id="@+id/child_container_stub" + android:inflatedId="@+id/notification_children_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + /> + + <ViewStub + android:layout="@layout/notification_expand_button" + android:id="@+id/more_button_stub" + android:inflatedId="@+id/notification_more_button_container" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_bottom_decor_height" + /> + + <ViewStub android:layout="@layout/notification_guts" android:id="@+id/notification_guts_stub" android:inflatedId="@+id/notification_guts" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 95a4009..c24cd64 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -376,6 +376,18 @@ <!-- radius of the corners of the material rounded rect background but negative--> <dimen name="notification_material_rounded_rect_radius_negative">-2dp</dimen> + <!-- height of the bottom decor below the notification if present (eg. an expand action) --> + <dimen name="notification_bottom_decor_height">48dp</dimen> + + <!-- The padding between notification children --> + <dimen name="notification_children_padding">2dp</dimen> + + <!-- The height of the divider between the notfication children --> + <dimen name="notification_children_divider_height">1dp</dimen> + + <!-- The vertical distance from which the notification appear when children are expanded --> + <dimen name="notification_appear_distance">140dp</dimen> + <!-- end margin for multi user switch in expanded quick settings --> <dimen name="multi_user_switch_expanded_margin">8dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 1b1b525..d9aff44 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -924,6 +924,12 @@ <!-- continue action for notification educating the user about enabling notifications on the lockscreen. [CHAR LIMIT=20] --> <string name="hidden_notifications_setup">Set up</string> + <!-- Text for the button to expand the notifications to show notification children [CHAR LIMIT=20] --> + <string name="notification_expand_button_text">See all</string> + + <!-- Text for the button to expand the notifications to hide notification children [CHAR LIMIT=20] --> + <string name="notification_collapse_button_text">Hide all</string> + <!-- Accessibility string for current zen mode and selected exit condition. A template that simply concatenates existing mode string and the current condition description. [CHAR LIMIT=20] --> <string name="zen_mode_and_condition"><xliff:g id="zen_mode" example="Priority interruptions only">%1$s</xliff:g>. <xliff:g id="exit_condition" example="For one hour">%2$s</xliff:g></string> diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java index aa7ce4e..bc7f745 100644 --- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java +++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java @@ -146,14 +146,14 @@ public class ExpandHelper implements Gefingerpoken { } public void setHeight(float h) { if (DEBUG_SCALE) Log.v(TAG, "SetHeight: setting to " + h); - mView.setActualHeight((int) h); + mView.setContentHeight((int) h); mCurrentHeight = h; } public float getHeight() { - return mView.getActualHeight(); + return mView.getContentHeight(); } public int getNaturalHeight(int maximum) { - return Math.min(maximum, mView.getMaxHeight()); + return Math.min(maximum, mView.getMaxContentHeight()); } } @@ -386,7 +386,8 @@ public class ExpandHelper implements Gefingerpoken { } private boolean isFullyExpanded(ExpandableView underFocus) { - return underFocus.getIntrinsicHeight() == underFocus.getMaxHeight(); + return underFocus.areChildrenExpanded() || underFocus.getIntrinsicHeight() + - underFocus.getBottomDecorHeight() == underFocus.getMaxContentHeight(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index e021cd4..0e5fd94 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -94,7 +94,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView = new PathInterpolator(0, 0, 0.5f, 1); private final int mTintedRippleColor; private final int mLowPriorityRippleColor; - private final int mNormalRippleColor; + protected final int mNormalRippleColor; private boolean mDimmed; private boolean mDark; @@ -115,7 +115,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private OnActivatedListener mOnActivatedListener; private final Interpolator mLinearOutSlowInInterpolator; - private final Interpolator mFastOutSlowInInterpolator; + protected final Interpolator mFastOutSlowInInterpolator; private final Interpolator mSlowOutFastInInterpolator; private final Interpolator mSlowOutLinearInInterpolator; private final Interpolator mLinearInterpolator; @@ -678,7 +678,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } - private int getRippleColor() { + protected int getRippleColor() { if (mBgTint != 0) { return mTintedRippleColor; } else if (mShowingLegacyBackground) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java new file mode 100644 index 0000000..87c12c2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java @@ -0,0 +1,48 @@ +/* + * 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 com.android.systemui.statusbar; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Button; + +/** + * A Button which doesn't have overlapping drawing commands + */ +public class AlphaOptimizedButton extends Button { + public AlphaOptimizedButton(Context context) { + super(context); + } + + public AlphaOptimizedButton(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AlphaOptimizedButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public AlphaOptimizedButton(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index d92ae44..fab7409 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -122,6 +122,8 @@ public abstract class BaseStatusBar extends SystemUI implements public static final boolean ENABLE_REMOTE_INPUT = Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.enable_remote_input", false); + public static final boolean ENABLE_CHILD_NOTIFICATIONS = Build.IS_DEBUGGABLE + && SystemProperties.getBoolean("debug.child_notifs", false); protected static final int MSG_SHOW_RECENT_APPS = 1019; protected static final int MSG_HIDE_RECENT_APPS = 1020; @@ -437,10 +439,12 @@ public abstract class BaseStatusBar extends SystemUI implements boolean isUpdate = mNotificationData.get(sbn.getKey()) != null || isHeadsUp(sbn.getKey()); - // Ignore children of notifications that have a summary, since we're not - // going to show them anyway. This is true also when the summary is canceled, + // In case we don't allow child notifications, we ignore children of + // notifications that have a summary, since we're not going to show them + // anyway. This is true also when the summary is canceled, // because children are automatically canceled by NoMan in that case. - if (mGroupManager.isChildInGroupWithSummary(sbn)) { + if (!ENABLE_CHILD_NOTIFICATIONS + && mGroupManager.isChildInGroupWithSummary(sbn)) { if (DEBUG) { Log.d(TAG, "Ignoring group child due to existing summary: " + sbn); } @@ -1352,6 +1356,7 @@ public abstract class BaseStatusBar extends SystemUI implements row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row, parent, false); row.setExpansionLogger(this, entry.notification.getKey()); + row.setGroupManager(mGroupManager); } workAroundBadLayerDrawableOpacity(row); @@ -1867,22 +1872,25 @@ public abstract class BaseStatusBar extends SystemUI implements entry.row.setSystemExpanded(top); } } + boolean isInvisibleChild = !mGroupManager.isVisible(entry.notification); boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification); if ((isLockscreenPublicMode() && !mShowLockscreenNotifications) || (onKeyguard && (visibleNotifications >= maxKeyguardNotifications - || !showOnKeyguard))) { + || !showOnKeyguard || isInvisibleChild))) { entry.row.setVisibility(View.GONE); - if (onKeyguard && showOnKeyguard) { + if (onKeyguard && showOnKeyguard && !isInvisibleChild) { mKeyguardIconOverflowContainer.getIconsView().addNotification(entry); } } else { boolean wasGone = entry.row.getVisibility() == View.GONE; entry.row.setVisibility(View.VISIBLE); - if (wasGone) { - // notify the scroller of a child addition - mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */); + if (!isInvisibleChild) { + if (wasGone) { + // notify the scroller of a child addition + mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */); + } + visibleNotifications++; } - visibleNotifications++; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java index c9f0260..15a092c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java @@ -162,20 +162,20 @@ public class DragDownHelper implements Gefingerpoken { ? RUBBERBAND_FACTOR_EXPANDABLE : RUBBERBAND_FACTOR_STATIC; float rubberband = heightDelta * rubberbandFactor; - if (expandable && (rubberband + child.getMinHeight()) > child.getMaxHeight()) { - float overshoot = (rubberband + child.getMinHeight()) - child.getMaxHeight(); + if (expandable && (rubberband + child.getMinHeight()) > child.getMaxContentHeight()) { + float overshoot = (rubberband + child.getMinHeight()) - child.getMaxContentHeight(); overshoot *= (1 - RUBBERBAND_FACTOR_STATIC); rubberband -= overshoot; } - child.setActualHeight((int) (child.getMinHeight() + rubberband)); + child.setContentHeight((int) (child.getMinHeight() + rubberband)); } private void cancelExpansion(final ExpandableView child) { - if (child.getActualHeight() == child.getMinHeight()) { + if (child.getContentHeight() == child.getMinHeight()) { return; } - ObjectAnimator anim = ObjectAnimator.ofInt(child, "actualHeight", - child.getActualHeight(), child.getMinHeight()); + ObjectAnimator anim = ObjectAnimator.ofInt(child, "contentHeight", + child.getContentHeight(), child.getMinHeight()); anim.setInterpolator(mInterpolator); anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS); anim.addListener(new AnimatorListenerAdapter() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 788db29..06a174e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -16,9 +16,13 @@ package com.android.systemui.statusbar; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; @@ -26,10 +30,24 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewStub; import android.view.accessibility.AccessibilityEvent; +import android.view.animation.LinearInterpolator; import android.widget.ImageView; + import com.android.systemui.R; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.phone.PhoneStatusBar; +import com.android.systemui.statusbar.stack.NotificationChildrenContainer; +import com.android.systemui.statusbar.stack.StackScrollState; +import com.android.systemui.statusbar.stack.StackStateAnimator; +import com.android.systemui.statusbar.stack.StackViewState; + +import java.util.List; public class ExpandableNotificationRow extends ActivatableNotificationView { + + private static final int DEFAULT_DIVIDER_ALPHA = 0x29; + private static final int COLORED_DIVIDER_ALPHA = 0x7B; + private final LinearInterpolator mLinearInterpolator = new LinearInterpolator(); private int mRowMinHeight; /** Does this row contain layouts that can adapt to row expansion */ @@ -69,7 +87,27 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private StatusBarNotification mStatusBarNotification; private boolean mIsHeadsUp; + private View mExpandButton; + private View mExpandButtonDivider; + private ViewStub mExpandButtonStub; + private ViewStub mChildrenContainerStub; + private NotificationGroupManager mGroupManager; + private View mExpandButtonContainer; + private boolean mChildrenExpanded; + private NotificationChildrenContainer mChildrenContainer; + private ValueAnimator mChildExpandAnimator; + private float mChildrenExpandProgress; + private float mExpandButtonStart; private ViewStub mGutsStub; + private boolean mHasExpandAction; + private boolean mIsSystemChildExpanded; + private OnClickListener mExpandClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + mGroupManager.setGroupExpanded(mStatusBarNotification, + !mChildrenExpanded); + } + }; public void setIconAnimationRunning(boolean running) { setIconAnimationRunning(running, mPublicLayout); @@ -119,6 +157,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public void setStatusBarNotification(StatusBarNotification statusBarNotification) { mStatusBarNotification = statusBarNotification; updateVetoButton(); + updateExpandButton(); } public StatusBarNotification getStatusBarNotification() { @@ -129,10 +168,101 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mIsHeadsUp = isHeadsUp; } + public void setGroupManager(NotificationGroupManager groupManager) { + mGroupManager = groupManager; + } + + public void addChildNotification(ExpandableNotificationRow row) { + addChildNotification(row, -1); + } + + /** + * Add a child notification to this view. + * + * @param row the row to add + * @param childIndex the index to add it at, if -1 it will be added at the end + */ + public void addChildNotification(ExpandableNotificationRow row, int childIndex) { + if (mChildrenContainer == null) { + mChildrenContainerStub.inflate(); + } + mChildrenContainer.addNotification(row, childIndex); + } + + public void removeChildNotification(ExpandableNotificationRow row) { + if (mChildrenContainer != null) { + mChildrenContainer.removeNotification(row); + } + } + + @Override + public boolean areChildrenExpanded() { + return mChildrenExpanded; + } + + public List<ExpandableNotificationRow> getNotificationChildren() { + return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren(); + } + + /** + * Apply the order given in the list to the children. + * + * @param childOrder the new list order + * @return whether the list order has changed + */ + public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) { + return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder); + } + + public void getChildrenStates(StackScrollState resultState) { + if (mChildrenExpanded) { + StackViewState parentState = resultState.getViewStateForView(this); + mChildrenContainer.getState(resultState, parentState); + } + } + + public void applyChildrenState(StackScrollState state) { + if (mChildrenExpanded) { + mChildrenContainer.applyState(state); + } + } + + public void prepareExpansionChanged(StackScrollState state) { + if (mChildrenExpanded) { + mChildrenContainer.prepareExpansionChanged(state); + } + } + + public void startChildAnimation(StackScrollState finalState, + StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration) { + if (mChildrenExpanded) { + mChildrenContainer.startAnimationToState(finalState, stateAnimator, withDelays, delay, + duration); + } + } + + public ExpandableNotificationRow getViewAtPosition(float y) { + if (!mChildrenExpanded) { + return this; + } else { + ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y); + return view == null ? this : view; + } + } + public NotificationGuts getGuts() { return mGuts; } + protected int calculateContentHeightFromActualHeight(int actualHeight) { + int realActualHeight = actualHeight; + if (hasBottomDecor()) { + realActualHeight -= getBottomDecorHeight(); + } + realActualHeight = Math.max(getMinHeight(), realActualHeight); + return realActualHeight; + } + public interface ExpansionLogger { public void logNotificationExpansion(String key, boolean userAction, boolean expanded); } @@ -194,6 +324,27 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mGutsStub = null; } }); + mExpandButtonStub = (ViewStub) findViewById(R.id.more_button_stub); + mExpandButtonStub.setOnInflateListener(new ViewStub.OnInflateListener() { + + @Override + public void onInflate(ViewStub stub, View inflated) { + mExpandButtonContainer = inflated; + mExpandButton = inflated.findViewById(R.id.notification_expand_button); + mExpandButtonDivider = inflated.findViewById(R.id.notification_expand_divider); + mExpandButtonContainer.setOnClickListener(mExpandClickListener); + } + }); + mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub); + mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() { + + @Override + public void onInflate(ViewStub stub, View inflated) { + mChildrenContainer = (NotificationChildrenContainer) inflated; + mChildrenContainer.setCollapseClickListener(mExpandClickListener); + updateChildrenVisibility(false); + } + }); mVetoButton = findViewById(R.id.veto); } @@ -203,6 +354,54 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } } + private void updateChildrenVisibility(boolean animated) { + if (mChildrenContainer == null) { + return; + } + if (mChildExpandAnimator != null) { + mChildExpandAnimator.cancel(); + } + float targetProgress = mChildrenExpanded ? 1.0f : 0.0f; + if (animated) { + if (mChildrenExpanded) { + mChildrenContainer.setVisibility(VISIBLE); + } + mExpandButtonStart = mExpandButtonContainer.getTranslationY(); + mChildExpandAnimator = ValueAnimator.ofFloat(mChildrenExpandProgress, targetProgress); + mChildExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + setChildrenExpandProgress((float) animation.getAnimatedValue()); + } + }); + mChildExpandAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mChildExpandAnimator = null; + if (!mChildrenExpanded) { + mChildrenContainer.setVisibility(INVISIBLE); + } + } + }); + mChildExpandAnimator.setInterpolator(mLinearInterpolator); + mChildExpandAnimator.setDuration( + StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED); + mChildExpandAnimator.start(); + } else { + setChildrenExpandProgress(targetProgress); + mChildrenContainer.setVisibility(mChildrenExpanded ? VISIBLE : INVISIBLE); + } + } + + private void setChildrenExpandProgress(float progress) { + mChildrenExpandProgress = progress; + updateExpandButtonAppearance(); + NotificationContentView showingLayout = getShowingLayout(); + float alpha = 1.0f - mChildrenExpandProgress; + alpha = PhoneStatusBar.ALPHA_OUT.getInterpolation(alpha); + showingLayout.setAlpha(alpha); + } + @Override public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { if (super.onRequestSendAccessibilityEventInternal(child, event)) { @@ -292,7 +491,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { if (expand != mIsSystemExpanded) { final boolean wasExpanded = isExpanded(); mIsSystemExpanded = expand; - notifyHeightChanged(); + notifyHeightChanged(false /* needsAnimation */); logExpansionEvent(false, wasExpanded); } } @@ -306,7 +505,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mExpansionDisabled = expansionDisabled; logExpansionEvent(false, wasExpanded); if (wasExpanded != isExpanded()) { - notifyHeightChanged(); + notifyHeightChanged(false /* needsAnimation */); } } } @@ -324,9 +523,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public void applyExpansionToLayout() { boolean expand = isExpanded(); if (expand && mExpandable) { - setActualHeight(mMaxExpandHeight); + setContentHeight(mMaxExpandHeight); } else { - setActualHeight(mRowMinHeight); + setContentHeight(mRowMinHeight); } } @@ -336,12 +535,26 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return getActualHeight(); } boolean inExpansionState = isExpanded(); - if (!inExpansionState) { - // not expanded, so we return the collapsed size - return mRowMinHeight; + int maxContentHeight; + if ((!inExpansionState && !mChildrenExpanded) || mShowingPublicForIntrinsicHeight) { + maxContentHeight = mRowMinHeight; + } else if (mChildrenExpanded) { + maxContentHeight = mChildrenContainer.getIntrinsicHeight(); + } else { + maxContentHeight = getMaxExpandHeight(); } + return maxContentHeight + getBottomDecorHeight(); + } - return mShowingPublicForIntrinsicHeight ? mRowMinHeight : getMaxExpandHeight(); + @Override + protected boolean hasBottomDecor() { + return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS + && !mIsHeadsUp && mGroupManager.hasGroupChildren(mStatusBarNotification); + } + + @Override + protected boolean canHaveBottomDecor() { + return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && !mIsHeadsUp; } /** @@ -354,7 +567,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { */ private boolean isExpanded() { return !mExpansionDisabled - && (!hasUserChangedExpansion() && isSystemExpanded() || isUserExpanded()); + && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded()) + || isUserExpanded()); + } + + private boolean isSystemChildExpanded() { + return mIsSystemChildExpanded; + } + + public void setSystemChildExpanded(boolean expanded) { + mIsSystemChildExpanded = expanded; } @Override @@ -368,11 +590,20 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mWasReset = false; } + @Override + protected boolean isChildInvisible(View child) { + + // We don't want to layout the ChildrenContainer if this is a heads-up view, otherwise the + // view will get too high and the shadows will be off. + boolean isInvisibleChildContainer = child == mChildrenContainer && mIsHeadsUp; + return super.isChildInvisible(child) || isInvisibleChildContainer; + } + private void updateMaxExpandHeight() { int intrinsicBefore = getIntrinsicHeight(); mMaxExpandHeight = mPrivateLayout.getMaxHeight(); if (intrinsicBefore != getIntrinsicHeight()) { - notifyHeightChanged(); + notifyHeightChanged(false /* needsAnimation */); } } @@ -439,8 +670,127 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE); } + public void setChildrenExpanded(boolean expanded, boolean animate) { + mChildrenExpanded = expanded; + updateChildrenVisibility(animate); + } + + public void updateExpandButton() { + boolean hasExpand = hasBottomDecor(); + if (hasExpand != mHasExpandAction) { + if (hasExpand) { + if (mExpandButtonContainer == null) { + mExpandButtonStub.inflate(); + } + mExpandButtonContainer.setVisibility(View.VISIBLE); + updateExpandButtonAppearance(); + updateExpandButtonColor(); + } else if (mExpandButtonContainer != null) { + mExpandButtonContainer.setVisibility(View.GONE); + } + notifyHeightChanged(true /* needsAnimation */); + } + mHasExpandAction = hasExpand; + } + + private void updateExpandButtonAppearance() { + if (mExpandButtonContainer == null) { + return; + } + float expandButtonAlpha = 0.0f; + float expandButtonTranslation = 0.0f; + float containerTranslation = 0.0f; + int minHeight = getMinHeight(); + if (!mChildrenExpanded || mChildExpandAnimator != null) { + int expandActionHeight = getBottomDecorHeight(); + int translationY = getActualHeight() - expandActionHeight; + if (translationY > minHeight) { + containerTranslation = translationY; + expandButtonAlpha = 1.0f; + expandButtonTranslation = 0.0f; + } else { + containerTranslation = minHeight; + float progress = expandActionHeight != 0 + ? (minHeight - translationY) / (float) expandActionHeight + : 1.0f; + expandButtonTranslation = -progress * expandActionHeight * 0.7f; + float alphaProgress = Math.min(progress / 0.7f, 1.0f); + alphaProgress = PhoneStatusBar.ALPHA_OUT.getInterpolation(alphaProgress); + expandButtonAlpha = 1.0f - alphaProgress; + } + } + if (mChildExpandAnimator != null || mChildrenExpanded) { + expandButtonAlpha = (1.0f - mChildrenExpandProgress) + * expandButtonAlpha; + expandButtonTranslation = (1.0f - mChildrenExpandProgress) + * expandButtonTranslation; + float newTranslation = -getBottomDecorHeight(); + + // We don't want to take the actual height of the view as this is already + // interpolated by a custom interpolator leading to a confusing animation. We want + // to have a stable end value to interpolate in between + float collapsedHeight = !mChildrenExpanded + ? Math.max(StackStateAnimator.getFinalActualHeight(this) + - getBottomDecorHeight(), minHeight) + : mExpandButtonStart; + float translationProgress = mFastOutSlowInInterpolator.getInterpolation( + mChildrenExpandProgress); + containerTranslation = (1.0f - translationProgress) * collapsedHeight + + translationProgress * newTranslation; + } + mExpandButton.setAlpha(expandButtonAlpha); + mExpandButtonDivider.setAlpha(expandButtonAlpha); + mExpandButton.setTranslationY(expandButtonTranslation); + mExpandButtonContainer.setTranslationY(containerTranslation); + NotificationContentView showingLayout = getShowingLayout(); + float layoutTranslation = + mExpandButtonContainer.getTranslationY() - showingLayout.getContentHeight(); + layoutTranslation = Math.min(layoutTranslation, 0); + if (!mChildrenExpanded && mChildExpandAnimator == null) { + // Needed for the DragDownHelper in order not to jump there, as the position + // can be negative for a short time. + layoutTranslation = 0; + } + showingLayout.setTranslationY(layoutTranslation); + if (mChildrenContainer != null) { + mChildrenContainer.setTranslationY( + mExpandButtonContainer.getTranslationY() + getBottomDecorHeight()); + } + } + + private void updateExpandButtonColor() { + // TODO: This needs some more baking, currently only the divider is colored according to + // the tint, but legacy black doesn't work yet perfectly for the button etc. + int color = getRippleColor(); + if (color == mNormalRippleColor) { + color = 0; + } + if (mExpandButtonDivider != null) { + applyTint(mExpandButtonDivider, color); + } + if (mChildrenContainer != null) { + mChildrenContainer.setTintColor(color); + } + } + + public static void applyTint(View v, int color) { + int alpha; + if (color != 0) { + alpha = COLORED_DIVIDER_ALPHA; + } else { + color = 0xff000000; + alpha = DEFAULT_DIVIDER_ALPHA; + } + if (v.getBackground() instanceof ColorDrawable) { + ColorDrawable background = (ColorDrawable) v.getBackground(); + background.mutate(); + background.setColor(color); + background.setAlpha(alpha); + } + } + public int getMaxExpandHeight() { - return mShowingPublicForIntrinsicHeight ? mRowMinHeight : mMaxExpandHeight; + return mMaxExpandHeight; } @Override @@ -451,17 +801,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { @Override public void setActualHeight(int height, boolean notifyListeners) { - mPrivateLayout.setActualHeight(height); - mPublicLayout.setActualHeight(height); + super.setActualHeight(height, notifyListeners); + int contentHeight = calculateContentHeightFromActualHeight(height); + mPrivateLayout.setContentHeight(contentHeight); + mPublicLayout.setContentHeight(contentHeight); if (mGuts != null) { mGuts.setActualHeight(height); } invalidate(); - super.setActualHeight(height, notifyListeners); + updateExpandButtonAppearance(); } @Override - public int getMaxHeight() { + public int getMaxContentHeight() { NotificationContentView showingLayout = getShowingLayout(); return showingLayout.getMaxHeight(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java index 1e5dcf7..7ae0d6d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java @@ -33,8 +33,8 @@ import java.util.ArrayList; */ public abstract class ExpandableView extends FrameLayout { - - private OnHeightChangedListener mOnHeightChangedListener; + private final int mBottomDecorHeight; + protected OnHeightChangedListener mOnHeightChangedListener; protected int mMaxViewHeight; private int mActualHeight; protected int mClipTopAmount; @@ -48,6 +48,12 @@ public abstract class ExpandableView extends FrameLayout { super(context, attrs); mMaxViewHeight = getResources().getDimensionPixelSize( R.dimen.notification_max_height); + mBottomDecorHeight = resolveBottomDecorHeight(); + } + + protected int resolveBottomDecorHeight() { + return getResources().getDimensionPixelSize( + R.dimen.notification_bottom_decor_height); } @Override @@ -65,6 +71,9 @@ public abstract class ExpandableView extends FrameLayout { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); + if (child.getVisibility() == GONE || isChildInvisible(child)) { + continue; + } int childHeightSpec = newHeightSpec; ViewGroup.LayoutParams layoutParams = child.getLayoutParams(); if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) { @@ -93,6 +102,10 @@ public abstract class ExpandableView extends FrameLayout { } mMatchParentViews.clear(); int width = MeasureSpec.getSize(widthMeasureSpec); + if (canHaveBottomDecor()) { + // We always account for the expandAction as well. + ownHeight += mBottomDecorHeight; + } setMeasuredDimension(width, ownHeight); } @@ -102,7 +115,7 @@ public abstract class ExpandableView extends FrameLayout { if (!mActualHeightInitialized && mActualHeight == 0) { int initialHeight = getInitialHeight(); if (initialHeight != 0) { - setActualHeight(initialHeight); + setContentHeight(initialHeight); } } } @@ -145,12 +158,12 @@ public abstract class ExpandableView extends FrameLayout { mActualHeight = actualHeight; updateClipping(); if (notifyListeners) { - notifyHeightChanged(); + notifyHeightChanged(false /* needsAnimation */); } } - public void setActualHeight(int actualHeight) { - setActualHeight(actualHeight, true); + public void setContentHeight(int contentHeight) { + setActualHeight(contentHeight + getBottomDecorHeight(), true); } /** @@ -163,14 +176,39 @@ public abstract class ExpandableView extends FrameLayout { } /** + * This view may have a bottom decor which will be placed below the content. If it has one, this + * view will be layouted higher than just the content by {@link #mBottomDecorHeight}. + * @return the height of the decor if it currently has one + */ + public int getBottomDecorHeight() { + return hasBottomDecor() ? mBottomDecorHeight : 0; + } + + /** + * @return whether this view may have a bottom decor at all. This will force the view to layout + * itself higher than just it's content + */ + protected boolean canHaveBottomDecor() { + return false; + } + + /** + * @return whether this view has a decor view below it's content. This will make the intrinsic + * height from {@link #getIntrinsicHeight()} higher as well + */ + protected boolean hasBottomDecor() { + return false; + } + + /** * @return The maximum height of this notification. */ - public int getMaxHeight() { + public int getMaxContentHeight() { return getHeight(); } /** - * @return The minimum height of this notification. + * @return The minimum content height of this notification. */ public int getMinHeight() { return getHeight(); @@ -249,9 +287,9 @@ public abstract class ExpandableView extends FrameLayout { return false; } - public void notifyHeightChanged() { + public void notifyHeightChanged(boolean needsAnimation) { if (mOnHeightChangedListener != null) { - mOnHeightChangedListener.onHeightChanged(this); + mOnHeightChangedListener.onHeightChanged(this, needsAnimation); } } @@ -302,6 +340,21 @@ public abstract class ExpandableView extends FrameLayout { outRect.top += getTranslationY() + getClipTopAmount(); } + public int getContentHeight() { + return mActualHeight - getBottomDecorHeight(); + } + + /** + * @return whether the given child can be ignored for layouting and measuring purposes + */ + protected boolean isChildInvisible(View child) { + return false; + } + + public boolean areChildrenExpanded() { + return false; + } + private void updateClipping() { mClipRect.set(0, mClipTopOptimization, getWidth(), getActualHeight()); setClipBounds(mClipRect); @@ -330,8 +383,9 @@ public abstract class ExpandableView extends FrameLayout { /** * @param view the view for which the height changed, or {@code null} if just the top * padding or the padding between the elements changed + * @param needsAnimation whether the view height needs to be animated */ - void onHeightChanged(ExpandableView view); + void onHeightChanged(ExpandableView view, boolean needsAnimation); /** * Called when the view is reset and therefore the height will change abruptly diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 70c270c..745e75d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -47,7 +47,7 @@ public class NotificationContentView extends FrameLayout { private int mSmallHeight; private int mClipTopAmount; - private int mActualHeight; + private int mContentHeight; private final Interpolator mLinearInterpolator = new LinearInterpolator(); @@ -97,7 +97,7 @@ public class NotificationContentView extends FrameLayout { mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); mContractedVisible = true; if (resetActualHeight) { - mActualHeight = mSmallHeight; + mContentHeight = mSmallHeight; } } @@ -154,12 +154,17 @@ public class NotificationContentView extends FrameLayout { } } - public void setActualHeight(int actualHeight) { - mActualHeight = actualHeight; + public void setContentHeight(int contentHeight) { + contentHeight = Math.max(Math.min(contentHeight, getHeight()), getMinHeight()); + mContentHeight = contentHeight; selectLayout(mAnimate /* animate */, false /* force */); updateClipping(); } + public int getContentHeight() { + return mContentHeight; + } + public int getMaxHeight() { // The maximum height is just the laid out height. @@ -176,7 +181,7 @@ public class NotificationContentView extends FrameLayout { } private void updateClipping() { - mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight); + mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight); setClipBounds(mClipBounds); } @@ -235,7 +240,7 @@ public class NotificationContentView extends FrameLayout { } private boolean showContractedChild() { - return mActualHeight <= mSmallHeight || mExpandedChild == null; + return mContentHeight <= mSmallHeight || mExpandedChild == null; } public void notifyContentUpdated() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 7e68c10..912f414 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -240,7 +240,8 @@ public class NotificationData { return true; } - if (mGroupManager.isChildInGroupWithSummary(sbn)) { + if (!BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS + && mGroupManager.isChildInGroupWithSummary(sbn)) { return true; } return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java index bfa3aa5..5fa7070 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java @@ -62,4 +62,13 @@ public class NotificationOverflowContainer extends ActivatableNotificationView { public NotificationOverflowIconsView getIconsView() { return mIconsView; } + + protected int getContentHeightFromActualHeight(int actualHeight) { + int realActualHeight = actualHeight; + if (hasBottomDecor()) { + realActualHeight -= getBottomDecorHeight(); + } + realActualHeight = Math.max(getMinHeight(), realActualHeight); + return realActualHeight; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java index 5214ab4..7072dcb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java @@ -100,6 +100,8 @@ public class NotificationGroupManager { } } }); + } else { + group.summary.row.updateExpandButton(); } } } @@ -116,11 +118,15 @@ public class NotificationGroupManager { } if (notif.isGroupSummary()) { group.summary = added; + group.expanded = added.row.areChildrenExpanded(); if (!group.children.isEmpty()) { mListener.onGroupCreatedFromChildren(group); } } else { group.children.add(added); + if (group.summary != null && group.children.size() == 1 && !group.expanded) { + group.summary.row.updateExpandButton(); + } } } 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 7513fc6..195da46 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -1680,7 +1680,7 @@ public class NotificationPanelView extends PanelView implements } @Override - public void onHeightChanged(ExpandableView view) { + public void onHeightChanged(ExpandableView view, boolean needsAnimation) { // Block update if we are in quick settings and just the top padding changed // (i.e. view == null). diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index e5eb747..f3ec34a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -170,6 +170,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -524,6 +525,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, goToLockedShade(null); } }; + private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap + = new HashMap<>(); @Override public void start() { @@ -664,6 +667,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, R.id.notification_stack_scroller); mStackScroller.setLongPressListener(getNotificationLongClicker()); mStackScroller.setPhoneStatusBar(this); + mStackScroller.setGroupManager(mGroupManager); + mGroupManager.setOnGroupChangeListener(mStackScroller); mKeyguardIconOverflowContainer = (NotificationOverflowContainer) LayoutInflater.from(mContext).inflate( @@ -855,9 +860,20 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren); for (int i = 0; i < numChildren; i++) { final View child = mStackScroller.getChildAt(i); - if (mStackScroller.canChildBeDismissed(child)) { - if (child.getVisibility() == View.VISIBLE) { - viewsToHide.add(child); + if (child instanceof ExpandableNotificationRow) { + if (mStackScroller.canChildBeDismissed(child)) { + if (child.getVisibility() == View.VISIBLE) { + viewsToHide.add(child); + } + } + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + List<ExpandableNotificationRow> children = row.getNotificationChildren(); + if (row.areChildrenExpanded() && children != null) { + for (ExpandableNotificationRow childRow : children) { + if (childRow.getVisibility() == View.VISIBLE) { + viewsToHide.add(childRow); + } + } } } } @@ -1296,10 +1312,23 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, ent.row.setShowingLegacyBackground(true); } } - toShow.add(ent.row); + if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) { + ExpandableNotificationRow summary = mGroupManager.getGroupSummary( + ent.row.getStatusBarNotification()); + List<ExpandableNotificationRow> orderedChildren = + mTmpChildOrderMap.get(summary); + if (orderedChildren == null) { + orderedChildren = new ArrayList<>(); + mTmpChildOrderMap.put(summary, orderedChildren); + } + orderedChildren.add(ent.row); + } else { + toShow.add(ent.row); + } + } - ArrayList<View> toRemove = new ArrayList<View>(); + ArrayList<View> toRemove = new ArrayList<>(); for (int i=0; i< mStackScroller.getChildCount(); i++) { View child = mStackScroller.getChildAt(i); if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) { @@ -1328,17 +1357,22 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, continue; } - if (child == toShow.get(j)) { - // Everything is well, advance both lists. - j++; - continue; + ExpandableNotificationRow targetChild = toShow.get(j); + if (child != targetChild) { + // Oops, wrong notification at this position. Put the right one + // here and advance both lists. + mStackScroller.changeViewPosition(targetChild, i); } - - // Oops, wrong notification at this position. Put the right one - // here and advance both lists. - mStackScroller.changeViewPosition(toShow.get(j), i); j++; + } + + // lets handle the child notifications now + updateNotificationShadeForChildren(); + + // clear the map again for the next usage + mTmpChildOrderMap.clear(); + updateRowStates(); updateSpeedbump(); updateClearAll(); @@ -1353,6 +1387,52 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mShadeUpdates.check(); } + private void updateNotificationShadeForChildren() { + ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); + boolean orderChanged = false; + for (int i = 0; i < mStackScroller.getChildCount(); i++) { + View view = mStackScroller.getChildAt(i); + if (!(view instanceof ExpandableNotificationRow)) { + // We don't care about non-notification views. + continue; + } + + ExpandableNotificationRow parent = (ExpandableNotificationRow) view; + List<ExpandableNotificationRow> children = parent.getNotificationChildren(); + List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); + + // lets first remove all undesired children + if (children != null) { + toRemove.clear(); + for (ExpandableNotificationRow childRow : children) { + if (orderedChildren == null || !orderedChildren.contains(childRow)) { + toRemove.add(childRow); + } + } + for (ExpandableNotificationRow remove : toRemove) { + parent.removeChildNotification(remove); + mStackScroller.notifyGroupChildRemoved(remove); + } + } + + // We now add all the children which are not in there already + for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size(); + childIndex++) { + ExpandableNotificationRow childView = orderedChildren.get(childIndex); + if (children == null || !children.contains(childView)) { + parent.addChildNotification(childView, childIndex); + mStackScroller.notifyGroupChildAdded(childView); + } + } + + // Finally after removing and adding has been beformed we can apply the order. + orderChanged |= parent.applyChildOrder(orderedChildren); + } + if (orderChanged) { + mStackScroller.generateChildOrderChangedEvent(); + } + } + private boolean packageHasVisibilityOverride(String key) { return mNotificationData.getVisibilityOverride(key) != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE; @@ -1379,6 +1459,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, final int N = activeNotifications.size(); for (int i = 0; i < N; i++) { Entry entry = activeNotifications.get(i); + boolean isChild = !isTopLevelChild(entry); + if (isChild) { + continue; + } if (entry.row.getVisibility() != View.GONE && mNotificationData.isAmbient(entry.key)) { speedbumpIndex = currentIndex; @@ -1389,6 +1473,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStackScroller.updateSpeedBumpIndex(speedbumpIndex); } + public static boolean isTopLevelChild(Entry entry) { + return entry.row.getParent() instanceof NotificationStackScrollLayout; + } + @Override protected void updateNotifications() { mNotificationData.filterAndSort(); @@ -3074,7 +3162,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mLeaveOpenOnKeyguardHide = false; if (mDraggedDownRow != null) { mDraggedDownRow.setUserLocked(false); - mDraggedDownRow.notifyHeightChanged(); + mDraggedDownRow.notifyHeightChanged(false /* needsAnimation */); mDraggedDownRow = null; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index 798467f..c49f620 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -158,13 +158,16 @@ public class StatusBarIconController { final int N = activeNotifications.size(); ArrayList<StatusBarIconView> toShow = new ArrayList<>(N); - // Filter out ambient notifications. + // Filter out ambient notifications and notification children. for (int i = 0; i < N; i++) { NotificationData.Entry ent = activeNotifications.get(i); if (notificationData.isAmbient(ent.key) && !NotificationData.showNotificationEvenIfUnprovisioned(ent.notification)) { continue; } + if (!PhoneStatusBar.isTopLevelChild(ent)) { + continue; + } toShow.add(ent.icon); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java index f8b8a9a..6c1cdcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java @@ -204,6 +204,9 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. } private void resetViewForHeadsup() { + if (mHeadsUp.row.areChildrenExpanded()) { + mHeadsUp.row.setChildrenExpanded(false /* expanded */, false /* animated */); + } mHeadsUp.row.setSystemExpanded(true); mHeadsUp.row.setSensitive(false); mHeadsUp.row.setHeadsUp(true); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java new file mode 100644 index 0000000..3c9e8cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java @@ -0,0 +1,406 @@ +/* + * 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 com.android.systemui.statusbar.stack; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.ExpandableView; + +import java.util.ArrayList; +import java.util.List; + +/** + * A container containing child notifications + */ +public class NotificationChildrenContainer extends ViewGroup { + + private final int mChildPadding; + private final int mDividerHeight; + private final int mMaxNotificationHeight; + private final List<View> mDividers = new ArrayList<>(); + private final List<ExpandableNotificationRow> mChildren = new ArrayList<>(); + private final View mCollapseButton; + private final View mCollapseDivider; + private final int mCollapseButtonHeight; + private final int mNotificationAppearDistance; + + public NotificationChildrenContainer(Context context) { + this(context, null); + } + + public NotificationChildrenContainer(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mChildPadding = getResources().getDimensionPixelSize( + R.dimen.notification_children_padding); + mDividerHeight = getResources().getDimensionPixelSize( + R.dimen.notification_children_divider_height); + mMaxNotificationHeight = getResources().getDimensionPixelSize( + R.dimen.notification_max_height); + mNotificationAppearDistance = getResources().getDimensionPixelSize( + R.dimen.notification_appear_distance); + LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class); + mCollapseButton = inflater.inflate(R.layout.notification_collapse_button, this, + false); + mCollapseButtonHeight = getResources().getDimensionPixelSize( + R.dimen.notification_bottom_decor_height); + addView(mCollapseButton); + mCollapseDivider = inflateDivider(); + addView(mCollapseDivider); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int childCount = mChildren.size(); + boolean firstChild = true; + for (int i = 0; i < childCount; i++) { + View child = mChildren.get(i); + boolean viewGone = child.getVisibility() == View.GONE; + if (i != 0) { + View divider = mDividers.get(i - 1); + int dividerVisibility = divider.getVisibility(); + int newVisibility = viewGone ? INVISIBLE : VISIBLE; + if (dividerVisibility != newVisibility) { + divider.setVisibility(newVisibility); + } + } + if (viewGone) { + continue; + } + child.layout(0, 0, getWidth(), child.getMeasuredHeight()); + if (!firstChild) { + mDividers.get(i - 1).layout(0, 0, getWidth(), mDividerHeight); + } else { + firstChild = false; + } + } + mCollapseButton.layout(0, 0, getWidth(), mCollapseButtonHeight); + mCollapseDivider.layout(0, mCollapseButtonHeight - mDividerHeight, getWidth(), + mCollapseButtonHeight); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int ownMaxHeight = mMaxNotificationHeight; + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY; + boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST; + if (hasFixedHeight || isHeightLimited) { + int size = MeasureSpec.getSize(heightMeasureSpec); + ownMaxHeight = Math.min(ownMaxHeight, size); + } + int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST); + int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY); + int collapseButtonHeightSpec = MeasureSpec.makeMeasureSpec(mCollapseButtonHeight, + MeasureSpec.EXACTLY); + mCollapseButton.measure(widthMeasureSpec, collapseButtonHeightSpec); + mCollapseDivider.measure(widthMeasureSpec, dividerHeightSpec); + int height = mCollapseButtonHeight; + int childCount = mChildren.size(); + boolean firstChild = true; + for (int i = 0; i < childCount; i++) { + View child = mChildren.get(i); + if (child.getVisibility() == View.GONE) { + continue; + } + child.measure(widthMeasureSpec, newHeightSpec); + height += child.getMeasuredHeight(); + if (!firstChild) { + // layout the divider + View divider = mDividers.get(i - 1); + divider.measure(widthMeasureSpec, dividerHeightSpec); + height += mChildPadding; + } else { + firstChild = false; + } + } + int width = MeasureSpec.getSize(widthMeasureSpec); + height = hasFixedHeight ? ownMaxHeight + : isHeightLimited ? Math.min(ownMaxHeight, height) + : height; + setMeasuredDimension(width, height); + } + + /** + * Add a child notification to this view. + * + * @param row the row to add + * @param childIndex the index to add it at, if -1 it will be added at the end + */ + public void addNotification(ExpandableNotificationRow row, int childIndex) { + int newIndex = childIndex < 0 ? mChildren.size() : childIndex; + mChildren.add(newIndex, row); + addView(row); + if (mChildren.size() != 1) { + View divider = inflateDivider(); + addView(divider); + mDividers.add(Math.max(newIndex - 1, 0), divider); + } + // TODO: adapt background corners + // TODO: fix overdraw + } + + public void removeNotification(ExpandableNotificationRow row) { + int childIndex = mChildren.indexOf(row); + mChildren.remove(row); + removeView(row); + if (!mDividers.isEmpty()) { + View divider = mDividers.remove(Math.max(childIndex - 1, 0)); + removeView(divider); + } + row.setSystemChildExpanded(false); + // TODO: adapt background corners + } + + private View inflateDivider() { + return LayoutInflater.from(mContext).inflate( + R.layout.notification_children_divider, this, false); + } + + public List<ExpandableNotificationRow> getNotificationChildren() { + return mChildren; + } + + /** + * Apply the order given in the list to the children. + * + * @param childOrder the new list order + * @return whether the list order has changed + */ + public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) { + if (childOrder == null) { + return false; + } + boolean result = false; + for (int i = 0; i < mChildren.size() && i < childOrder.size(); i++) { + ExpandableNotificationRow child = mChildren.get(i); + ExpandableNotificationRow desiredChild = childOrder.get(i); + if (child != desiredChild) { + mChildren.remove(desiredChild); + mChildren.add(i, desiredChild); + result = true; + } + } + + // Let's make the first child expanded! + boolean first = true; + for (int i = 0; i < childOrder.size(); i++) { + ExpandableNotificationRow child = childOrder.get(i); + child.setSystemChildExpanded(first); + first = false; + } + return result; + } + + public int getIntrinsicHeight() { + int childCount = mChildren.size(); + int intrinsicHeight = 0; + int visibleChildren = 0; + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + if (child.getVisibility() == View.GONE) { + continue; + } + intrinsicHeight += child.getIntrinsicHeight(); + visibleChildren++; + } + if (visibleChildren > 0) { + intrinsicHeight += (visibleChildren - 1) * mDividerHeight; + } + return intrinsicHeight; + } + + /** + * Update the state of all its children based on a linear layout algorithm. + * + * @param resultState the state to update + * @param parentState the state of the parent + */ + public void getState(StackScrollState resultState, StackViewState parentState) { + int childCount = mChildren.size(); + int yPosition = mCollapseButtonHeight; + boolean firstChild = true; + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + if (child.getVisibility() == View.GONE) { + continue; + } + if (!firstChild) { + // There's a divider + yPosition += mChildPadding; + } else { + firstChild = false; + } + StackViewState childState = resultState.getViewStateForView(child); + int intrinsicHeight = child.getIntrinsicHeight(); + childState.yTranslation = yPosition; + childState.zTranslation = 0; + childState.height = intrinsicHeight; + childState.dimmed = parentState.dimmed; + childState.dark = parentState.dark; + childState.hideSensitive = parentState.hideSensitive; + childState.belowSpeedBump = parentState.belowSpeedBump; + childState.scale = parentState.scale; + childState.clipTopAmount = 0; + childState.topOverLap = 0; + childState.location = parentState.location; + yPosition += intrinsicHeight; + } + } + + public void applyState(StackScrollState state) { + int childCount = mChildren.size(); + boolean firstChild = true; + ViewState dividerState = new ViewState(); + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + StackViewState viewState = state.getViewStateForView(child); + if (child.getVisibility() == View.GONE) { + continue; + } + if (!firstChild) { + // layout the divider + View divider = mDividers.get(i - 1); + dividerState.initFrom(divider); + dividerState.yTranslation = (int) (viewState.yTranslation + - (mChildPadding + mDividerHeight) / 2.0f); + dividerState.alpha = 1; + state.applyViewState(divider, dividerState); + } else { + firstChild = false; + } + state.applyState(child, viewState); + } + } + + public void setCollapseClickListener(OnClickListener collapseClickListener) { + mCollapseButton.setOnClickListener(collapseClickListener); + } + + /** + * This is called when the children expansion has changed and positions the children properly + * for an appear animation. + * + * @param state the new state we animate to + */ + public void prepareExpansionChanged(StackScrollState state) { + int childCount = mChildren.size(); + boolean firstChild = true; + StackViewState sourceState = new StackViewState(); + ViewState dividerState = new ViewState(); + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + StackViewState viewState = state.getViewStateForView(child); + if (child.getVisibility() == View.GONE) { + continue; + } + if (!firstChild) { + // layout the divider + View divider = mDividers.get(i - 1); + dividerState.initFrom(divider); + dividerState.yTranslation = viewState.yTranslation + - (mChildPadding + mDividerHeight) / 2.0f + mNotificationAppearDistance; + dividerState.alpha = 0; + state.applyViewState(divider, dividerState); + } else { + firstChild = false; + } + sourceState.copyFrom(viewState); + sourceState.alpha = 0; + sourceState.yTranslation += mNotificationAppearDistance; + state.applyState(child, sourceState); + } + mCollapseButton.setAlpha(0); + mCollapseDivider.setAlpha(0); + mCollapseDivider.setTranslationY(mNotificationAppearDistance / 4); + } + + public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator, + boolean withDelays, long baseDelay, long duration) { + int childCount = mChildren.size(); + boolean firstChild = true; + ViewState dividerState = new ViewState(); + int notGoneIndex = 0; + for (int i = 0; i < childCount; i++) { + ExpandableNotificationRow child = mChildren.get(i); + StackViewState viewState = state.getViewStateForView(child); + if (child.getVisibility() == View.GONE) { + continue; + } + int difference = Math.min(StackStateAnimator.DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN, + notGoneIndex + 1); + long delay = withDelays + ? difference * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN + : 0; + delay += baseDelay; + if (!firstChild) { + // layout the divider + View divider = mDividers.get(i - 1); + dividerState.initFrom(divider); + dividerState.yTranslation = viewState.yTranslation + - (mChildPadding + mDividerHeight) / 2.0f; + dividerState.alpha = 1; + stateAnimator.startViewAnimations(divider, dividerState, delay, duration); + } else { + firstChild = false; + } + stateAnimator.startStackAnimations(child, viewState, state, -1, delay); + notGoneIndex++; + } + dividerState.initFrom(mCollapseButton); + dividerState.alpha = 1.0f; + stateAnimator.startViewAnimations(mCollapseButton, dividerState, baseDelay, duration); + dividerState.initFrom(mCollapseDivider); + dividerState.alpha = 1.0f; + dividerState.yTranslation = 0.0f; + stateAnimator.startViewAnimations(mCollapseDivider, dividerState, baseDelay, duration); + } + + public ExpandableNotificationRow getViewAtPosition(float y) { + // find the view under the pointer, accounting for GONE views + final int count = mChildren.size(); + for (int childIdx = 0; childIdx < count; childIdx++) { + ExpandableNotificationRow slidingChild = mChildren.get(childIdx); + float childTop = slidingChild.getTranslationY(); + float top = childTop + slidingChild.getClipTopAmount(); + float bottom = childTop + slidingChild.getActualHeight(); + if (y >= top && y <= bottom) { + return slidingChild; + } + } + return null; + } + + public void setTintColor(int color) { + ExpandableNotificationRow.applyTint(mCollapseDivider, color); + } +} 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 4c7dbbb..2eafd57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -41,9 +41,11 @@ import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; +import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.SpeedBumpView; import com.android.systemui.statusbar.StackScrollerDecorView; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.PhoneStatusBar; import com.android.systemui.statusbar.policy.ScrollAdapter; @@ -55,7 +57,7 @@ import java.util.HashSet; */ public class NotificationStackScrollLayout extends ViewGroup implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter, - ExpandableView.OnHeightChangedListener { + ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener { private static final String TAG = "NotificationStackScrollLayout"; private static final boolean DEBUG = false; @@ -118,6 +120,7 @@ public class NotificationStackScrollLayout extends ViewGroup */ private StackScrollState mCurrentStackScrollState = new StackScrollState(this); private AmbientState mAmbientState = new AmbientState(); + private NotificationGroupManager mGroupManager; private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>(); private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>(); private ArrayList<View> mSnappedBackChildren = new ArrayList<View>(); @@ -180,6 +183,7 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mDontReportNextOverScroll; private boolean mRequestViewResizeAnimationOnLayout; private boolean mNeedViewResizeAnimation; + private View mExpandedGroupView; private boolean mEverythingNeedsAnimation; /** @@ -213,6 +217,7 @@ public class NotificationStackScrollLayout extends ViewGroup }; private PhoneStatusBar mPhoneStatusBar; private int[] mTempInt2 = new int[2]; + private boolean mGenerateChildOrderChangedEvent; private boolean mRemoveAnimationEnabled; public NotificationStackScrollLayout(Context context) { @@ -309,7 +314,7 @@ public class NotificationStackScrollLayout extends ViewGroup private void notifyHeightChangeListener(ExpandableView view) { if (mOnHeightChangedListener != null) { - mOnHeightChangedListener.onHeightChanged(view); + mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */); } } @@ -329,6 +334,9 @@ public class NotificationStackScrollLayout extends ViewGroup float centerX = getWidth() / 2.0f; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } float width = child.getMeasuredWidth(); float height = child.getMeasuredHeight(); child.layout((int) (centerX - width / 2.0f), @@ -339,16 +347,18 @@ public class NotificationStackScrollLayout extends ViewGroup setMaxLayoutHeight(getHeight()); updateContentHeight(); clampScrollPosition(); - requestAnimationOnViewResize(); + if (mRequestViewResizeAnimationOnLayout) { + requestAnimationOnViewResize(); + mRequestViewResizeAnimationOnLayout = false; + } requestChildrenUpdate(); } private void requestAnimationOnViewResize() { - if (mRequestViewResizeAnimationOnLayout && mIsExpanded && mAnimationsEnabled) { + if (mIsExpanded && mAnimationsEnabled) { mNeedViewResizeAnimation = true; mNeedsAnimation = true; } - mRequestViewResizeAnimationOnLayout = false; } public void updateSpeedBumpIndex(int newIndex) { @@ -645,6 +655,10 @@ public class NotificationStackScrollLayout extends ViewGroup int right = getWidth(); if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { + if (slidingChild instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; + return row.getViewAtPosition(touchY - childTop); + } return slidingChild; } } @@ -1543,6 +1557,14 @@ public class NotificationStackScrollLayout extends ViewGroup @Override protected void onViewRemoved(View child) { super.onViewRemoved(child); + // we only call our internal methods if this is actually a removal and not just a + // notification which becomes a child notification + if (!isChildInGroup(child)) { + onViewRemovedInternal(child); + } + } + + private void onViewRemovedInternal(View child) { mStackScrollAlgorithm.notifyChildrenChanged(this); if (mChangePositionInProgress) { // This is only a position change, don't do anything special @@ -1568,6 +1590,12 @@ public class NotificationStackScrollLayout extends ViewGroup ((ExpandableView) child).setClipTopOptimization(0); } + private boolean isChildInGroup(View child) { + return child instanceof ExpandableNotificationRow + && mGroupManager.isChildInGroupWithSummary( + ((ExpandableNotificationRow) child).getStatusBarNotification()); + } + /** * Generate a remove animation for a child view. * @@ -1575,7 +1603,7 @@ public class NotificationStackScrollLayout extends ViewGroup * @return Whether an animation was generated. */ private boolean generateRemoveAnimation(View child) { - if (mIsExpanded && mAnimationsEnabled) { + if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) { if (!mChildrenToAddAnimated.contains(child)) { // Generate Animations mChildrenToRemoveAnimated.add(child); @@ -1591,6 +1619,23 @@ public class NotificationStackScrollLayout extends ViewGroup } /** + * @param child the child to query + * @return whether a view is not a top level child but a child notification and that group is + * not expanded + */ + private boolean isChildInInvisibleGroup(View child) { + if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + ExpandableNotificationRow groupSummary = + mGroupManager.getGroupSummary(row.getStatusBarNotification()); + if (groupSummary != null && groupSummary != row) { + return !groupSummary.areChildrenExpanded(); + } + } + return false; + } + + /** * Updates the scroll position when a child was removed * * @param removedChild the removed child @@ -1638,6 +1683,10 @@ public class NotificationStackScrollLayout extends ViewGroup @Override protected void onViewAdded(View child) { super.onViewAdded(child); + onViewAddedInternal(child); + } + + private void onViewAddedInternal(View child) { mStackScrollAlgorithm.notifyChildrenChanged(this); ((ExpandableView) child).setOnHeightChangedListener(this); generateAddAnimation(child, false /* fromMoreCard */); @@ -1650,6 +1699,14 @@ public class NotificationStackScrollLayout extends ViewGroup } } + public void notifyGroupChildRemoved(View row) { + onViewRemovedInternal(row); + } + + public void notifyGroupChildAdded(View row) { + onViewAddedInternal(row); + } + public void setAnimationsEnabled(boolean animationsEnabled) { mAnimationsEnabled = animationsEnabled; updateNotificationAnimationStates(); @@ -1745,10 +1802,20 @@ public class NotificationStackScrollLayout extends ViewGroup generateDarkEvent(); generateGoToFullShadeEvent(); generateViewResizeEvent(); + generateGroupExpansionEvent(); generateAnimateEverythingEvent(); mNeedsAnimation = false; } + private void generateGroupExpansionEvent() { + // Generate a group expansion/collapsing event if there is such a group at all + if (mExpandedGroupView != null) { + mAnimationEvents.add(new AnimationEvent(mExpandedGroupView, + AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED)); + mExpandedGroupView = null; + } + } + private void generateViewResizeEvent() { if (mNeedViewResizeAnimation) { mAnimationEvents.add( @@ -1795,6 +1862,11 @@ public class NotificationStackScrollLayout extends ViewGroup AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); } mChildrenChangingPositions.clear(); + if (mGenerateChildOrderChangedEvent) { + mAnimationEvents.add(new AnimationEvent(null, + AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); + mGenerateChildOrderChangedEvent = false; + } } private void generateChildAdditionEvents() { @@ -2063,11 +2135,14 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - public void onHeightChanged(ExpandableView view) { + public void onHeightChanged(ExpandableView view, boolean needsAnimation) { updateContentHeight(); updateScrollPositionOnExpandInBottom(view); clampScrollPosition(); notifyHeightChangeListener(view); + if (needsAnimation) { + requestAnimationOnViewResize(); + } requestChildrenUpdate(); } @@ -2410,6 +2485,10 @@ public class NotificationStackScrollLayout extends ViewGroup this.mPhoneStatusBar = phoneStatusBar; } + public void setGroupManager(NotificationGroupManager groupManager) { + this.mGroupManager = groupManager; + } + public void onGoToKeyguard() { requestAnimateEverything(); } @@ -2455,6 +2534,51 @@ public class NotificationStackScrollLayout extends ViewGroup mRemoveAnimationEnabled = enabled; } + private void updateExpandButtons() { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + row.updateExpandButton(); + } + } + } + + @Override + public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) { + boolean animated = mAnimationsEnabled && mIsExpanded; + if (animated) { + mExpandedGroupView = changedRow; + mNeedsAnimation = true; + } + changedRow.setChildrenExpanded(expanded, animated); + onHeightChanged(changedRow, false /* needsAnimation */); + } + + @Override + public void onGroupsProhibitedChanged() { + updateExpandButtons(); + } + + @Override + public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) { + for (NotificationData.Entry entry : group.children) { + ExpandableNotificationRow row = entry.row; + if (indexOfChild(row) != -1) { + removeView(row); + group.summary.row.addChildNotification(row); + } + } + } + + public void generateChildOrderChangedEvent() { + if (mIsExpanded && mAnimationsEnabled) { + mGenerateChildOrderChangedEvent = true; + mNeedsAnimation = true; + requestChildrenUpdate(); + } + } + /** * A listener that is notified when some child locations might have changed. */ @@ -2591,6 +2715,14 @@ public class NotificationStackScrollLayout extends ViewGroup .animateY() .animateZ(), + // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED + new AnimationFilter() + .animateAlpha() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ(), + // ANIMATION_TYPE_EVERYTHING new AnimationFilter() .animateAlpha() @@ -2645,6 +2777,9 @@ public class NotificationStackScrollLayout extends ViewGroup // ANIMATION_TYPE_VIEW_RESIZE StackStateAnimator.ANIMATION_DURATION_STANDARD, + // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED + StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED, + // ANIMATION_TYPE_EVERYTHING StackStateAnimator.ANIMATION_DURATION_STANDARD, }; @@ -2662,7 +2797,8 @@ public class NotificationStackScrollLayout extends ViewGroup static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10; static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11; static final int ANIMATION_TYPE_VIEW_RESIZE = 12; - static final int ANIMATION_TYPE_EVERYTHING = 13; + static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13; + static final int ANIMATION_TYPE_EVERYTHING = 14; static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1; static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2; 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 2c124d0..e7bf47b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -27,6 +27,7 @@ import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import java.util.ArrayList; +import java.util.List; /** * The Algorithm of the {@link com.android.systemui.statusbar.stack @@ -171,6 +172,19 @@ public class StackScrollAlgorithm { updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState); updateClipping(resultState, algorithmState); updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex()); + getNotificationChildrenStates(resultState, algorithmState); + } + + private void getNotificationChildrenStates(StackScrollState resultState, + StackScrollAlgorithmState algorithmState) { + int childCount = algorithmState.visibleChildren.size(); + for (int i = 0; i < childCount; i++) { + ExpandableView v = algorithmState.visibleChildren.get(i); + if (v instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + row.getChildrenStates(resultState); + } + } } private void updateSpeedBumpState(StackScrollState resultState, @@ -328,6 +342,23 @@ public class StackScrollAlgorithm { viewState.notGoneIndex = notGoneIndex; state.visibleChildren.add(v); notGoneIndex++; + + // handle the notgoneIndex for the children as well + if (v instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + List<ExpandableNotificationRow> children = + row.getNotificationChildren(); + if (row.areChildrenExpanded() && children != null) { + for (ExpandableNotificationRow childRow : children) { + if (childRow.getVisibility() != View.GONE) { + StackViewState childState + = resultState.getViewStateForView(childRow); + childState.notGoneIndex = notGoneIndex; + notGoneIndex++; + } + } + } + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java index ffd700d..feae590 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -23,10 +23,12 @@ import android.view.ViewGroup; import com.android.systemui.R; import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.EmptyShadeView; +import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.SpeedBumpView; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -57,6 +59,18 @@ public class StackScrollState { for (int i = 0; i < numChildren; i++) { ExpandableView child = (ExpandableView) mHostView.getChildAt(i); resetViewState(child); + + // handling reset for child notifications + if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + List<ExpandableNotificationRow> children = + row.getNotificationChildren(); + if (row.areChildrenExpanded() && children != null) { + for (ExpandableNotificationRow childRow : children) { + resetViewState(childRow); + } + } + } } } @@ -154,6 +168,10 @@ public class StackScrollState { if (oldClipTopOptimization != state.topOverLap) { view.setClipTopOptimization(state.topOverLap); } + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) view; + row.applyChildrenState(this); + } return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java index 47f0114..b249fbf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -26,6 +26,7 @@ import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.SpeedBumpView; @@ -42,12 +43,15 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_STANDARD = 360; public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448; public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464; + public static final int ANIMATION_DURATION_EXPAND_CLICKED = 360; public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80; + public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54; public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32; public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48; public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24; - private static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2; + public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2; + public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN = 3; private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag; private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag; @@ -85,6 +89,7 @@ public class StackStateAnimator { private ValueAnimator mTopOverScrollAnimator; private ValueAnimator mBottomOverScrollAnimator; + private ExpandableNotificationRow mChildExpandingView; public StackStateAnimator(NotificationStackScrollLayout hostLayout) { mHostLayout = hostLayout; @@ -127,6 +132,7 @@ public class StackStateAnimator { } mNewEvents.clear(); mNewAddChildren.clear(); + mChildExpandingView = null; } private int findLastNotAddedIndex(StackScrollState finalState) { @@ -216,6 +222,10 @@ public class StackStateAnimator { if (child instanceof SpeedBumpView) { finalState.performSpeedBumpAnimation(i, (SpeedBumpView) child, viewState, delay + duration); + } else if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + row.startChildAnimation(finalState, this, child == mChildExpandingView, delay, + duration); } } @@ -813,6 +823,11 @@ public class StackStateAnimator { // A race condition can trigger the view to be added to the overlay even though // it is swiped out. So let's remove it mHostLayout.getOverlay().remove(changingView); + } else if (event.animationType == NotificationStackScrollLayout + .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) { + ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView; + row.prepareExpansionChanged(finalState); + mChildExpandingView = row; } mNewEvents.add(event); } |