diff options
author | Jorim Jaggi <jjaggi@google.com> | 2014-11-17 19:14:04 +0100 |
---|---|---|
committer | Jorim Jaggi <jjaggi@google.com> | 2014-11-19 16:52:43 +0100 |
commit | 4e857f4ef0357e05806819d0488a73a12208fe8f (patch) | |
tree | 70dcb8e48cd765ec22b4b2e2a46ab265a9c214f4 /packages | |
parent | 1ca6af34b48b18b8c4b5e2cdf3f1bd4295d8ac2e (diff) | |
download | frameworks_base-4e857f4ef0357e05806819d0488a73a12208fe8f.zip frameworks_base-4e857f4ef0357e05806819d0488a73a12208fe8f.tar.gz frameworks_base-4e857f4ef0357e05806819d0488a73a12208fe8f.tar.bz2 |
Fade notifications when exiting dozing
Bug: 18146441
Change-Id: Ifa0cbb8157c65768c3a0c584166ca88c3016b730
Diffstat (limited to 'packages')
16 files changed, 592 insertions, 88 deletions
diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml index ef85847..4526af5 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml @@ -32,6 +32,7 @@ /> <LinearLayout + android:id="@+id/content" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView diff --git a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java new file mode 100644 index 0000000..2ff8f8a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2014 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; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.view.View; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; + +import com.android.systemui.statusbar.phone.NotificationPanelView; + +/** + * Helper to invert the colors of views and fade between the states. + */ +public class ViewInvertHelper { + + private final Paint mDarkPaint = new Paint(); + private final Interpolator mLinearOutSlowInInterpolator; + private final View mTarget; + private final ColorMatrix mMatrix = new ColorMatrix(); + private final ColorMatrix mGrayscaleMatrix = new ColorMatrix(); + private final long mFadeDuration; + + public ViewInvertHelper(View target, long fadeDuration) { + mTarget = target; + mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(mTarget.getContext(), + android.R.interpolator.linear_out_slow_in); + mFadeDuration = fadeDuration; + } + + public void fade(final boolean invert, long delay) { + float startIntensity = invert ? 0f : 1f; + float endIntensity = invert ? 1f : 0f; + ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + updateInvertPaint((Float) animation.getAnimatedValue()); + mTarget.setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (!invert) { + mTarget.setLayerType(View.LAYER_TYPE_NONE, null); + } + } + }); + animator.setDuration(mFadeDuration); + animator.setInterpolator(mLinearOutSlowInInterpolator); + animator.setStartDelay(delay); + animator.start(); + } + + public void update(boolean invert) { + if (invert) { + updateInvertPaint(1f); + mTarget.setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint); + } else { + mTarget.setLayerType(View.LAYER_TYPE_NONE, null); + } + } + + public View getTarget() { + return mTarget; + } + + private void updateInvertPaint(float intensity) { + float components = 1 - 2 * intensity; + final float[] invert = { + components, 0f, 0f, 0f, 255f * intensity, + 0f, components, 0f, 0f, 255f * intensity, + 0f, 0f, components, 0f, 255f * intensity, + 0f, 0f, 0f, 1f, 0f + }; + mMatrix.set(invert); + mGrayscaleMatrix.setSaturation(1 - intensity); + mMatrix.preConcat(mGrayscaleMatrix); + mDarkPaint.setColorFilter(new ColorMatrixColorFilter(mMatrix)); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index 02b9378..7b60307 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -43,6 +43,7 @@ import android.view.animation.LinearInterpolator; import android.view.animation.PathInterpolator; import com.android.systemui.R; +import com.android.systemui.statusbar.phone.NotificationPanelView; /** * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer} @@ -53,6 +54,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private static final long DOUBLETAP_TIMEOUT_MS = 1200; private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220; private static final int ACTIVATE_ANIMATION_LENGTH = 220; + private static final int DARK_ANIMATION_LENGTH = 170; /** * The amount of width, which is kept in the end when performing a disappear animation (also @@ -84,6 +86,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView */ private static final float VERTICAL_ANIMATION_START = 1.0f; + /** + * Scale for the background to animate from when exiting dark mode. + */ + private static final float DARK_EXIT_SCALE_START = 0.93f; + private static final Interpolator ACTIVATE_INVERSE_INTERPOLATOR = new PathInterpolator(0.6f, 0, 0.5f, 1); private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR @@ -94,7 +101,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private boolean mDimmed; private boolean mDark; - private final Paint mDarkPaint = createDarkPaint(); private int mBgTint = 0; private final int mRoundedRectCornerRadius; @@ -332,40 +338,32 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView if (mDimmed != dimmed) { mDimmed = dimmed; if (fade) { - fadeBackground(); + fadeDimmedBackground(); } else { updateBackground(); } } } - public void setDark(boolean dark, boolean fade) { - // TODO implement fade - if (mDark != dark) { - mDark = dark; - if (mDark) { - setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint); + public void setDark(boolean dark, boolean fade, long delay) { + super.setDark(dark, fade, delay); + if (mDark == dark) { + return; + } + mDark = dark; + if (!dark && fade) { + if (mActivated) { + mBackgroundDimmed.setVisibility(View.VISIBLE); + mBackgroundNormal.setVisibility(View.VISIBLE); } else { - setLayerType(View.LAYER_TYPE_NONE, null); + mBackgroundDimmed.setVisibility(View.VISIBLE); + mBackgroundNormal.setVisibility(View.INVISIBLE); } + fadeDarkToDimmed(delay); + } else { + updateBackground(); } - } - - private static Paint createDarkPaint() { - final Paint p = new Paint(); - final float[] invert = { - -1f, 0f, 0f, 1f, 1f, - 0f, -1f, 0f, 1f, 1f, - 0f, 0f, -1f, 1f, 1f, - 0f, 0f, 0f, 1f, 0f - }; - final ColorMatrix m = new ColorMatrix(invert); - final ColorMatrix grayscale = new ColorMatrix(); - grayscale.setSaturation(0); - m.preConcat(grayscale); - p.setColorFilter(new ColorMatrixColorFilter(m)); - return p; - } + } public void setShowingLegacyBackground(boolean showing) { mShowingLegacyBackground = showing; @@ -402,7 +400,39 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundNormal.setRippleColor(rippleColor); } - private void fadeBackground() { + /** + * Fades the dimmed background when exiting dark mode. + */ + private void fadeDarkToDimmed(long delay) { + mBackgroundDimmed.setAlpha(0f); + mBackgroundDimmed.setPivotX(mBackgroundDimmed.getWidth() / 2f); + mBackgroundDimmed.setPivotY(getActualHeight() / 2f); + mBackgroundDimmed.setScaleX(DARK_EXIT_SCALE_START); + mBackgroundDimmed.setScaleY(DARK_EXIT_SCALE_START); + mBackgroundDimmed.animate() + .alpha(1f) + .scaleX(1f) + .scaleY(1f) + .setDuration(DARK_ANIMATION_LENGTH) + .setStartDelay(delay) + .setInterpolator(mLinearOutSlowInInterpolator) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + // Jump state if we are cancelled + mBackgroundDimmed.setScaleX(1f); + mBackgroundDimmed.setScaleY(1f); + mBackgroundDimmed.setAlpha(1f); + } + }) + .start(); + } + + /** + * Fades the background when the dimmed state changes. + */ + private void fadeDimmedBackground() { + mBackgroundDimmed.animate().cancel(); mBackgroundNormal.animate().cancel(); if (mDimmed) { mBackgroundDimmed.setVisibility(View.VISIBLE); @@ -443,11 +473,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } private void updateBackground() { - if (mDimmed) { + cancelFadeAnimations(); + if (mDark) { + mBackgroundDimmed.setVisibility(View.INVISIBLE); + mBackgroundNormal.setVisibility(View.INVISIBLE); + } else if (mDimmed) { mBackgroundDimmed.setVisibility(View.VISIBLE); mBackgroundNormal.setVisibility(View.INVISIBLE); } else { - cancelFadeAnimations(); mBackgroundDimmed.setVisibility(View.INVISIBLE); mBackgroundNormal.setVisibility(View.VISIBLE); mBackgroundNormal.setAlpha(1f); @@ -459,6 +492,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView if (mBackgroundAnimator != null) { mBackgroundAnimator.cancel(); } + mBackgroundDimmed.animate().cancel(); mBackgroundNormal.animate().cancel(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 7345440..8ad8406 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -207,11 +207,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } @Override - public void setDark(boolean dark, boolean fade) { - super.setDark(dark, fade); + public void setDark(boolean dark, boolean fade, long delay) { + super.setDark(dark, fade, delay); final NotificationContentView showing = getShowingLayout(); if (showing != null) { - showing.setDark(dark, fade); + showing.setDark(dark, fade, delay); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java index bf1e78e..ebc663c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java @@ -39,6 +39,7 @@ public abstract class ExpandableView extends FrameLayout { private int mActualHeight; protected int mClipTopAmount; private boolean mActualHeightInitialized; + private boolean mDark; private ArrayList<View> mMatchParentViews = new ArrayList<View>(); public ExpandableView(Context context, AttributeSet attrs) { @@ -185,8 +186,14 @@ public abstract class ExpandableView extends FrameLayout { * * @param dark Whether the notification should be dark. * @param fade Whether an animation should be played to change the state. + * @param delay If fading, the delay of the animation. */ - public void setDark(boolean dark, boolean fade) { + public void setDark(boolean dark, boolean fade, long delay) { + mDark = dark; + } + + public boolean isDark() { + return mDark; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 99214a0..582b8d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -42,14 +42,14 @@ import com.android.systemui.R; public class NotificationContentView extends FrameLayout { private static final long ANIMATION_DURATION_LENGTH = 170; - private static final Paint INVERT_PAINT = createInvertPaint(); - private static final ColorFilter NO_COLOR_FILTER = new ColorFilter(); private final Rect mClipBounds = new Rect(); private View mContractedChild; private View mExpandedChild; + private NotificationViewWrapper mContractedWrapper; + private int mSmallHeight; private int mClipTopAmount; private int mActualHeight; @@ -122,6 +122,7 @@ public class NotificationContentView extends FrameLayout { sanitizeContractedLayoutParams(child); addView(child); mContractedChild = child; + mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child); selectLayout(false /* animate */, true /* force */); } @@ -249,38 +250,10 @@ public class NotificationContentView extends FrameLayout { return mExpandedChild != null; } - public void setDark(boolean dark, boolean fade) { + public void setDark(boolean dark, boolean fade, long delay) { if (mDark == dark || mContractedChild == null) return; mDark = dark; - setImageViewDark(dark, fade, com.android.internal.R.id.right_icon); - setImageViewDark(dark, fade, com.android.internal.R.id.icon); - } - - private void setImageViewDark(boolean dark, boolean fade, int imageViewId) { - // TODO: implement fade - final ImageView v = (ImageView) mContractedChild.findViewById(imageViewId); - if (v == null) return; - final Drawable d = v.getBackground(); - if (dark) { - v.setLayerType(LAYER_TYPE_HARDWARE, INVERT_PAINT); - if (d != null) { - v.setTag(R.id.doze_saved_filter_tag, d.getColorFilter() != null ? d.getColorFilter() - : NO_COLOR_FILTER); - d.setColorFilter(getResources().getColor(R.color.doze_small_icon_background_color), - PorterDuff.Mode.SRC_ATOP); - v.setImageAlpha(getResources().getInteger(R.integer.doze_small_icon_alpha)); - } - } else { - v.setLayerType(LAYER_TYPE_NONE, null); - if (d != null) { - final ColorFilter filter = (ColorFilter) v.getTag(R.id.doze_saved_filter_tag); - if (filter != null) { - d.setColorFilter(filter == NO_COLOR_FILTER ? null : filter); - v.setTag(R.id.doze_saved_filter_tag, null); - } - v.setImageAlpha(0xff); - } - } + mContractedWrapper.setDark(dark, fade, delay); } @Override @@ -290,16 +263,4 @@ public class NotificationContentView extends FrameLayout { // layout, and saves us some layers. return false; } - - private static Paint createInvertPaint() { - final Paint p = new Paint(); - final float[] invert = { - -1f, 0f, 0f, 1f, 1f, - 0f, -1f, 0f, 1f, 1f, - 0f, 0f, -1f, 1f, 1f, - 0f, 0f, 0f, 1f, 0f - }; - p.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(invert))); - return p; - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java new file mode 100644 index 0000000..045be3e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 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.view.View; + +import com.android.systemui.ViewInvertHelper; +import com.android.systemui.statusbar.phone.NotificationPanelView; + +/** + * Wraps a notification containing a custom view. + */ +public class NotificationCustomViewWrapper extends NotificationViewWrapper { + + private final ViewInvertHelper mInvertHelper; + private boolean mDark; + + protected NotificationCustomViewWrapper(View view) { + super(view); + mInvertHelper = new ViewInvertHelper(view, NotificationPanelView.DOZE_ANIMATION_DURATION); + } + + @Override + public void setDark(boolean dark, boolean fade, long delay) { + if (mDark != dark) { + mDark = dark; + if (fade) { + mInvertHelper.fade(dark, delay); + } else { + mInvertHelper.update(dark); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java new file mode 100644 index 0000000..8f63a79 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 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.view.View; + +/** + * Wraps a media notification. + */ +public class NotificationMediaViewWrapper extends NotificationTemplateViewWrapper { + + private boolean mDark; + + protected NotificationMediaViewWrapper(Context ctx, View view) { + super(ctx, view); + } + + @Override + public void setDark(boolean dark, boolean fade, long delay) { + if (mDark != dark) { + mDark = dark; + + // Only update the large icon, because the rest is already inverted. + setPictureGrayscale(dark, fade, delay); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java index edfd205..bfa3aa5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java @@ -21,6 +21,8 @@ import android.util.AttributeSet; import android.widget.TextView; import com.android.systemui.R; +import com.android.systemui.ViewInvertHelper; +import com.android.systemui.statusbar.phone.NotificationPanelView; /** * Container view for overflowing notification icons on Keyguard. @@ -28,6 +30,8 @@ import com.android.systemui.R; public class NotificationOverflowContainer extends ActivatableNotificationView { private NotificationOverflowIconsView mIconsView; + private ViewInvertHelper mViewInvertHelper; + private boolean mDark; public NotificationOverflowContainer(Context context, AttributeSet attrs) { super(context, attrs); @@ -39,6 +43,20 @@ public class NotificationOverflowContainer extends ActivatableNotificationView { mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view); mIconsView.setMoreText((TextView) findViewById(R.id.more_text)); mIconsView.setOverflowIndicator(findViewById(R.id.more_icon_overflow)); + mViewInvertHelper = new ViewInvertHelper(findViewById(R.id.content), + NotificationPanelView.DOZE_ANIMATION_DURATION); + } + + @Override + public void setDark(boolean dark, boolean fade, long delay) { + super.setDark(dark, fade, delay); + if (mDark == dark) return; + mDark = dark; + if (fade) { + mViewInvertHelper.fade(dark, delay); + } else { + mViewInvertHelper.update(dark); + } } public NotificationOverflowIconsView getIconsView() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java new file mode 100644 index 0000000..8dc14b0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2014 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.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.view.View; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.widget.ImageView; + +import com.android.systemui.R; +import com.android.systemui.ViewInvertHelper; +import com.android.systemui.statusbar.phone.NotificationPanelView; + +/** + * Wraps a notification view inflated from a template. + */ +public class NotificationTemplateViewWrapper extends NotificationViewWrapper { + + private final ViewInvertHelper mInvertHelper; + private final ImageView mIcon; + protected final ImageView mPicture; + private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix(); + private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter( + 0, PorterDuff.Mode.SRC_ATOP); + private final int mIconDarkAlpha; + private final int mIconBackgroundColor; + private final int mIconBackgroundDarkColor; + private final Interpolator mLinearOutSlowInInterpolator; + + private boolean mDark; + + protected NotificationTemplateViewWrapper(Context ctx, View view) { + super(view); + mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha); + mIconBackgroundDarkColor = + ctx.getResources().getColor(R.color.doze_small_icon_background_color); + mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx, + android.R.interpolator.linear_out_slow_in); + View mainColumn = view.findViewById(com.android.internal.R.id.notification_main_column); + mInvertHelper = mainColumn != null + ? new ViewInvertHelper(mainColumn, NotificationPanelView.DOZE_ANIMATION_DURATION) + : null; + ImageView largeIcon = (ImageView) view.findViewById(com.android.internal.R.id.icon); + ImageView rightIcon = (ImageView) view.findViewById(com.android.internal.R.id.right_icon); + mIcon = resolveIcon(largeIcon, rightIcon); + mPicture = resolvePicture(largeIcon); + mIconBackgroundColor = resolveBackgroundColor(mIcon); + } + + private ImageView resolveIcon(ImageView largeIcon, ImageView rightIcon) { + return largeIcon != null && largeIcon.getBackground() != null ? largeIcon + : rightIcon != null && rightIcon.getBackground() != null ? rightIcon + : null; + } + + private ImageView resolvePicture(ImageView largeIcon) { + return largeIcon != null && largeIcon.getBackground() == null + ? largeIcon + : null; + } + + private int resolveBackgroundColor(ImageView icon) { + if (icon != null && icon.getBackground() != null) { + ColorFilter filter = icon.getBackground().getColorFilter(); + if (filter instanceof PorterDuffColorFilter) { + return ((PorterDuffColorFilter) filter).getColor(); + } + } + return 0; + } + + @Override + public void setDark(boolean dark, boolean fade, long delay) { + if (mDark != dark) { + mDark = dark; + if (mInvertHelper != null) { + if (fade) { + mInvertHelper.fade(dark, delay); + } else { + mInvertHelper.update(dark); + } + } + if (mIcon != null) { + if (fade) { + fadeIconColorFilter(mIcon, dark, delay); + fadeIconAlpha(mIcon, dark, delay); + } else { + updateIconColorFilter(mIcon, dark); + updateIconAlpha(mIcon, dark); + } + } + setPictureGrayscale(dark, fade, delay); + } + } + + protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) { + if (mPicture != null) { + if (fade) { + fadeGrayscale(mPicture, grayscale, delay); + } else { + updateGrayscale(mPicture, grayscale); + } + } + } + + private void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener, + boolean dark, long delay, Animator.AnimatorListener listener) { + float startIntensity = dark ? 0f : 1f; + float endIntensity = dark ? 1f : 0f; + ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity); + animator.addUpdateListener(updateListener); + animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION); + animator.setInterpolator(mLinearOutSlowInInterpolator); + animator.setStartDelay(delay); + if (listener != null) { + animator.addListener(listener); + } + animator.start(); + } + + private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) { + startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + updateIconColorFilter(target, (Float) animation.getAnimatedValue()); + } + }, dark, delay, null /* listener */); + } + + private void fadeIconAlpha(final ImageView target, boolean dark, long delay) { + startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = (float) animation.getAnimatedValue(); + target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t)); + } + }, dark, delay, null /* listener */); + } + + protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) { + startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + updateGrayscaleMatrix((float) animation.getAnimatedValue()); + target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); + } + }, dark, delay, new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (!dark) { + target.setColorFilter(null); + } + } + }); + } + + private void updateIconColorFilter(ImageView target, boolean dark) { + updateIconColorFilter(target, dark ? 1f : 0f); + } + + private void updateIconColorFilter(ImageView target, float intensity) { + int color = interpolateColor(mIconBackgroundColor, mIconBackgroundDarkColor, intensity); + mIconColorFilter.setColor(color); + target.getBackground().mutate().setColorFilter(mIconColorFilter); + } + + private void updateIconAlpha(ImageView target, boolean dark) { + target.setImageAlpha(dark ? mIconDarkAlpha : 255); + } + + protected void updateGrayscale(ImageView target, boolean dark) { + if (dark) { + updateGrayscaleMatrix(1f); + target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); + } else { + target.setColorFilter(null); + } + } + + private void updateGrayscaleMatrix(float intensity) { + mGrayscaleColorMatrix.setSaturation(1 - intensity); + } + + private static int interpolateColor(int source, int target, float t) { + int aSource = Color.alpha(source); + int rSource = Color.red(source); + int gSource = Color.green(source); + int bSource = Color.blue(source); + int aTarget = Color.alpha(target); + int rTarget = Color.red(target); + int gTarget = Color.green(target); + int bTarget = Color.blue(target); + return Color.argb( + (int) (aSource * (1f - t) + aTarget * t), + (int) (rSource * (1f - t) + rTarget * t), + (int) (gSource * (1f - t) + gTarget * t), + (int) (bSource * (1f - t) + bTarget * t)); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java new file mode 100644 index 0000000..0a02573 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 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.view.View; + +import com.android.internal.R; + +/** + * Wraps the actual notification content view; used to implement behaviors which are different for + * the individual templates and custom views. + */ +public abstract class NotificationViewWrapper { + + protected final View mView; + + public static NotificationViewWrapper wrap(Context ctx, View v) { + + // TODO: Figure out a better way to find out which template the view is. + if (v.findViewById(com.android.internal.R.id.media_actions) != null) { + return new NotificationMediaViewWrapper(ctx, v); + } else if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) { + return new NotificationTemplateViewWrapper(ctx, v); + } else { + return new NotificationCustomViewWrapper(v); + } + } + + protected NotificationViewWrapper(View view) { + mView = view; + } + + /** + * In dark mode, we draw as little as possible, assuming a black background. + * + * @param dark whether we should display ourselves in dark mode + * @param fade whether to animate the transition if the mode changes + * @param delay if fading, the delay of the animation + */ + public abstract void setDark(boolean dark, boolean fade, long delay); +} 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 f5c994a..709a074 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -3689,14 +3689,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (mState != StatusBarState.KEYGUARD && !mNotificationPanel.isDozing()) { return; } - mNotificationPanel.setDozing(mDozing, mDozeScrimController.isPulsing() /*animate*/); - if (mDozing) { - mStackScroller.setDark(true, false /*animate*/); - } else { - mStackScroller.setDark(false, false /*animate*/); - } + boolean animate = !mDozing && mDozeScrimController.isPulsing(); + mNotificationPanel.setDozing(mDozing, animate); + mStackScroller.setDark(mDozing, animate); mScrimController.setDozing(mDozing); - mDozeScrimController.setDozing(mDozing, mDozeScrimController.isPulsing() /* animate */); + mDozeScrimController.setDozing(mDozing, animate); } public void updateStackScrollerState(boolean goingToFullShade) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java index 3c93b19..3d4cda6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java @@ -33,6 +33,7 @@ public class AnimationFilter { boolean animateHideSensitive; boolean hasDelays; boolean hasGoToFullShadeEvent; + boolean hasDarkEvent; public AnimationFilter animateAlpha() { animateAlpha = true; @@ -98,6 +99,10 @@ public class AnimationFilter { NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE) { hasGoToFullShadeEvent = true; } + if (events.get(i).animationType == + NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_DARK) { + hasDarkEvent = true; + } } } @@ -126,5 +131,6 @@ public class AnimationFilter { animateHideSensitive = false; hasDelays = false; hasGoToFullShadeEvent = false; + hasDarkEvent = false; } } 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 e63be97..c5f1161 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -2455,7 +2455,8 @@ public class NotificationStackScrollLayout extends ViewGroup // ANIMATION_TYPE_DARK new AnimationFilter() - .animateDark(), + .animateDark() + .hasDelays(), // ANIMATION_TYPE_GO_TO_FULL_SHADE new AnimationFilter() 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 4611370..0b1ce8f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -154,7 +154,7 @@ public class StackScrollState { child.setDimmed(state.dimmed, false /* animate */); // apply dark - child.setDark(state.dark, false /* animate */); + child.setDark(state.dark, false /* animate */, 0 /* delay */); // apply hiding sensitive child.setHideSensitive( 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 a56440c..05077bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -46,6 +46,7 @@ public class StackStateAnimator { public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80; 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; private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag; @@ -161,11 +162,12 @@ public class StackStateAnimator { boolean scaleChanging = child.getScaleX() != viewState.scale; boolean alphaChanging = alpha != child.getAlpha(); boolean heightChanging = viewState.height != child.getActualHeight(); + boolean darkChanging = viewState.dark != child.isDark(); boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount(); boolean wasAdded = mNewAddChildren.contains(child); boolean hasDelays = mAnimationFilter.hasDelays; boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging || - alphaChanging || heightChanging || topInsetChanging; + alphaChanging || heightChanging || topInsetChanging || darkChanging; boolean noAnimation = wasAdded; long delay = 0; long duration = mCurrentLength; @@ -242,7 +244,7 @@ public class StackStateAnimator { && !noAnimation); // start dark animation - child.setDark(viewState.dark, mAnimationFilter.animateDark && !noAnimation); + child.setDark(viewState.dark, mAnimationFilter.animateDark && !noAnimation, delay); // apply speed bump state child.setBelowSpeedBump(viewState.belowSpeedBump); @@ -262,6 +264,9 @@ public class StackStateAnimator { private long calculateChildAnimationDelay(StackScrollState.ViewState viewState, StackScrollState finalState) { + if (mAnimationFilter.hasDarkEvent) { + return calculateDelayDark(viewState); + } if (mAnimationFilter.hasGoToFullShadeEvent) { return calculateDelayGoToFullShade(viewState); } @@ -309,6 +314,10 @@ public class StackStateAnimator { return minDelay; } + private long calculateDelayDark(StackScrollState.ViewState viewState) { + return viewState.notGoneIndex * ANIMATION_DELAY_PER_ELEMENT_DARK; + } + private long calculateDelayGoToFullShade(StackScrollState.ViewState viewState) { float index = viewState.notGoneIndex; index = (float) Math.pow(index, 0.7f); |