}
return this;
}
+
+ public ImageView getExpandButton() {
+ return mExpandButton;
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
}
<!-- For notification icons for which targetSdk < L, this caches whether the icon is grayscale -->
<item type="id" name="icon_is_grayscale" />
+ <item type="id" name="clip_children_tag" />
+ <item type="id" name="clip_children_set_tag" />
+ <item type="id" name="clip_to_padding_tag" />
+ <item type="id" name="image_icon_tag" />
<item type="id" name="is_clicked_heads_up_tag" />
</resources>
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.view.View;
+
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+
+/**
+ * A helper to fade views in and out.
+ */
+public class CrossFadeHelper {
+ public static final long ANIMATION_DURATION_LENGTH = 210;
+
+ public static void fadeOut(final View view, final Runnable endRunnable) {
+ view.animate().cancel();
+ view.animate()
+ .alpha(0f)
+ .setDuration(ANIMATION_DURATION_LENGTH)
+ .setInterpolator(PhoneStatusBar.ALPHA_OUT)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ view.setVisibility(View.INVISIBLE);
+ }
+ });
+ if (view.hasOverlappingRendering()) {
+ view.animate().withLayer();
+ }
+
+ }
+
+ public static void fadeIn(final View view) {
+ view.animate().cancel();
+ if (view.getVisibility() == View.INVISIBLE) {
+ view.setAlpha(0.0f);
+ view.setVisibility(View.VISIBLE);
+ }
+ view.animate()
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION_LENGTH)
+ .setInterpolator(PhoneStatusBar.ALPHA_IN);
+ if (view.hasOverlappingRendering()) {
+ view.animate().withLayer();
+ }
+ }
+}
mPrivateLayout.updateExpandButtons(isExpandable());
}
+ @Override
+ public void setClipToActualHeight(boolean clipToActualHeight) {
+ super.setClipToActualHeight(clipToActualHeight);
+ getShowingLayout().setClipToActualHeight(clipToActualHeight);
+ }
+
/**
* @return whether the user has changed the expansion state
*/
addView(mNotificationHeader, indexOfChild(mChildrenContainer) + 1);
} else {
header.reapply(getContext(), mNotificationHeader);
- mNotificationHeaderWrapper.notifyContentUpdated();
+ mNotificationHeaderWrapper.notifyContentUpdated(mEntry.notification);
}
updateHeaderExpandButton();
updateChildrenHeaderAppearance();
private static Rect mClipRect = new Rect();
private boolean mWillBeGone;
private int mMinClipTopAmount = 0;
+ private boolean mClipToActualHeight = true;
public ExpandableView(Context context, AttributeSet attrs) {
super(context, attrs);
}
private void updateClipping() {
- int top = mClipTopOptimization;
- if (top >= getActualHeight()) {
- top = getActualHeight() - 1;
+ if (mClipToActualHeight) {
+ int top = mClipTopOptimization;
+ if (top >= getActualHeight()) {
+ top = getActualHeight() - 1;
+ }
+ mClipRect.set(0, top, getWidth(), getActualHeight());
+ setClipBounds(mClipRect);
+ } else {
+ setClipBounds(null);
}
- mClipRect.set(0, top, getWidth(), getActualHeight());
- setClipBounds(mClipRect);
+ }
+
+ public void setClipToActualHeight(boolean clipToActualHeight) {
+ mClipToActualHeight = clipToActualHeight;
+ updateClipping();
}
public int getClipTopOptimization() {
import android.app.RemoteInput;
import android.content.Context;
import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.os.Build;
import android.service.notification.StatusBarNotification;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import com.android.systemui.R;
*/
public class NotificationContentView extends FrameLayout {
- private static final long ANIMATION_DURATION_LENGTH = 170;
private static final int VISIBLE_TYPE_CONTRACTED = 0;
private static final int VISIBLE_TYPE_EXPANDED = 1;
private static final int VISIBLE_TYPE_HEADSUP = 2;
private final Rect mClipBounds = new Rect();
private final int mRoundRectRadius;
- private final Interpolator mLinearInterpolator = new LinearInterpolator();
private final boolean mRoundRectClippingEnabled;
private final int mMinContractedHeight;
private int mUnrestrictedContentHeight;
private int mVisibleType = VISIBLE_TYPE_CONTRACTED;
private boolean mDark;
- private final Paint mFadePaint = new Paint();
private boolean mAnimate;
private boolean mIsHeadsUp;
private boolean mShowingLegacyBackground;
private OnClickListener mExpandClickListener;
private boolean mBeforeN;
private boolean mExpandable;
+ private boolean mClipToActualHeight = true;
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mHybridViewManager = new HybridNotificationViewManager(getContext(), this);
- mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
mRoundRectRadius = getResources().getDimensionPixelSize(
R.dimen.notification_material_rounded_rect_radius);
mRoundRectClippingEnabled = getResources().getBoolean(
}
private void updateClipping() {
- mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight);
- setClipBounds(mClipBounds);
+ if (mClipToActualHeight) {
+ mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight);
+ setClipBounds(mClipBounds);
+ } else {
+ setClipBounds(null);
+ }
+ }
+
+ public void setClipToActualHeight(boolean clipToActualHeight) {
+ mClipToActualHeight = clipToActualHeight;
+ updateClipping();
}
private void selectLayout(boolean animate, boolean force) {
|| (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null)
|| (visibleType == VISIBLE_TYPE_SINGLELINE && mSingleLineView != null)
|| visibleType == VISIBLE_TYPE_CONTRACTED)) {
- runSwitchAnimation(visibleType);
+ animateToVisibleType(visibleType);
} else {
updateViewVisibilities(visibleType);
}
private void updateViewVisibilities(int visibleType) {
boolean contractedVisible = visibleType == VISIBLE_TYPE_CONTRACTED;
- mContractedChild.setVisibility(contractedVisible ? View.VISIBLE : View.INVISIBLE);
- mContractedChild.setAlpha(contractedVisible ? 1f : 0f);
- mContractedChild.setLayerType(LAYER_TYPE_NONE, null);
+ mContractedWrapper.setVisible(contractedVisible);
if (mExpandedChild != null) {
boolean expandedVisible = visibleType == VISIBLE_TYPE_EXPANDED;
- mExpandedChild.setVisibility(expandedVisible ? View.VISIBLE : View.INVISIBLE);
- mExpandedChild.setAlpha(expandedVisible ? 1f : 0f);
- mExpandedChild.setLayerType(LAYER_TYPE_NONE, null);
+ mExpandedWrapper.setVisible(expandedVisible);
}
if (mHeadsUpChild != null) {
boolean headsUpVisible = visibleType == VISIBLE_TYPE_HEADSUP;
- mHeadsUpChild.setVisibility(headsUpVisible ? View.VISIBLE : View.INVISIBLE);
- mHeadsUpChild.setAlpha(headsUpVisible ? 1f : 0f);
- mHeadsUpChild.setLayerType(LAYER_TYPE_NONE, null);
+ mHeadsUpWrapper.setVisible(headsUpVisible);
}
if (mSingleLineView != null) {
boolean singleLineVisible = visibleType == VISIBLE_TYPE_SINGLELINE;
- mSingleLineView.setVisibility(singleLineVisible ? View.VISIBLE : View.INVISIBLE);
- mSingleLineView.setAlpha(singleLineVisible ? 1f : 0f);
- mSingleLineView.setLayerType(LAYER_TYPE_NONE, null);
+ mSingleLineView.setVisible(singleLineVisible);
}
- setLayerType(LAYER_TYPE_NONE, null);
updateRoundRectClipping();
}
- private void runSwitchAnimation(int visibleType) {
- View shownView = getViewForVisibleType(visibleType);
- View hiddenView = getViewForVisibleType(mVisibleType);
- shownView.setVisibility(View.VISIBLE);
- hiddenView.setVisibility(View.VISIBLE);
- shownView.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
- hiddenView.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
- setLayerType(LAYER_TYPE_HARDWARE, null);
- hiddenView.animate()
- .alpha(0f)
- .setDuration(ANIMATION_DURATION_LENGTH)
- .setInterpolator(mLinearInterpolator)
- .withEndAction(null); // In case we have multiple changes in one frame.
- shownView.animate()
- .alpha(1f)
- .setDuration(ANIMATION_DURATION_LENGTH)
- .setInterpolator(mLinearInterpolator)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- updateViewVisibilities(mVisibleType);
- }
- });
+ private void animateToVisibleType(int visibleType) {
+ final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
+ final TransformableView hiddenView = getTransformableViewForVisibleType(mVisibleType);
+ shownView.transformFrom(hiddenView);
+ getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
+ hiddenView.transformTo(shownView, new Runnable() {
+ @Override
+ public void run() {
+ hiddenView.setVisible(false);
+ }
+ });
updateRoundRectClipping();
}
/**
* @param visibleType one of the static enum types in this view
+ * @return the corresponding transformable view according to the given visible type
+ */
+ private TransformableView getTransformableViewForVisibleType(int visibleType) {
+ switch (visibleType) {
+ case VISIBLE_TYPE_EXPANDED:
+ return mExpandedWrapper;
+ case VISIBLE_TYPE_HEADSUP:
+ return mHeadsUpWrapper;
+ case VISIBLE_TYPE_SINGLELINE:
+ return mSingleLineView;
+ default:
+ return mContractedWrapper;
+ }
+ }
+
+ /**
+ * @param visibleType one of the static enum types in this view
* @return the corresponding view according to the given visible type
*/
private View getViewForVisibleType(int visibleType) {
applyRemoteInput(entry);
selectLayout(false /* animate */, true /* force */);
if (mContractedChild != null) {
- mContractedWrapper.notifyContentUpdated();
+ mContractedWrapper.notifyContentUpdated(entry.notification);
mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
}
if (mExpandedChild != null) {
- mExpandedWrapper.notifyContentUpdated();
+ mExpandedWrapper.notifyContentUpdated(entry.notification);
}
if (mHeadsUpChild != null) {
- mHeadsUpWrapper.notifyContentUpdated();
+ mHeadsUpWrapper.notifyContentUpdated(entry.notification);
}
updateRoundRectClipping();
}
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
+import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.animation.AnimationUtils;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.notification.TransformState;
import com.android.systemui.statusbar.phone.NotificationPanelView;
import java.util.ArrayList;
protected final Interpolator mLinearOutSlowInInterpolator;
protected final ViewInvertHelper mInvertHelper;
+ protected final ViewTransformationHelper mTransformationHelper;
+
protected int mColor;
private ImageView mIcon;
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx,
android.R.interpolator.linear_out_slow_in);
mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
+ mTransformationHelper = new ViewTransformationHelper();
resolveHeaderViews();
+ updateInvertHelper();
}
protected void resolveHeaderViews() {
mColor = resolveColor(mExpandButton);
mNotificationHeader = (NotificationHeaderView) mView.findViewById(
com.android.internal.R.id.notification_header);
- for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
- View child = mNotificationHeader.getChildAt(i);
- if (child != mIcon) {
- mInvertHelper.addTarget(child);
- }
- }
}
private int resolveColor(ImageView icon) {
}
@Override
- public void notifyContentUpdated() {
- mInvertHelper.clearTargets();
+ public void notifyContentUpdated(StatusBarNotification notification) {
// Reinspect the notification.
resolveHeaderViews();
+ updateInvertHelper();
+ updateTransformedTypes();
+ }
+
+ protected void updateInvertHelper() {
+ mInvertHelper.clearTargets();
+ for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
+ View child = mNotificationHeader.getChildAt(i);
+ if (child != mIcon) {
+ mInvertHelper.addTarget(child);
+ }
+ }
+ }
+
+ protected void updateTransformedTypes() {
+ mTransformationHelper.reset();
+ mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_HEADER, mNotificationHeader);
}
@Override
public NotificationHeaderView getNotificationHeader() {
return mNotificationHeader;
}
+
+ @Override
+ public TransformState getCurrentState(int fadingView) {
+ return mTransformationHelper.getCurrentState(fadingView);
+ }
+
+ @Override
+ public void transformTo(TransformableView notification, Runnable endRunnable) {
+ mTransformationHelper.transformTo(notification, endRunnable);
+ }
+
+ @Override
+ public void transformFrom(TransformableView notification) {
+ mTransformationHelper.transformFrom(notification);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ mTransformationHelper.setVisible(visible);
+ }
}
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
+import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.systemui.statusbar.notification.ImageTransformState;
+import com.android.systemui.statusbar.notification.TransformState;
+
+import java.util.ArrayList;
/**
* Wraps a notification view inflated from a template.
protected ImageView mPicture;
private ProgressBar mProgressBar;
+ private TextView mTitle;
+ private TextView mText;
protected NotificationTemplateViewWrapper(Context ctx, View view) {
super(ctx, view);
- resolveTemplateViews();
}
- private void resolveTemplateViews() {
- View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
+ private void resolveTemplateViews(StatusBarNotification notification) {
mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
+ if (notification != null) {
+ mPicture.setTag(ImageTransformState.ICON_TAG,
+ notification.getNotification().getLargeIcon());
+ }
+ mTitle = (TextView) mView.findViewById(com.android.internal.R.id.title);
+ mText = (TextView) mView.findViewById(com.android.internal.R.id.text);
final View progress = mView.findViewById(com.android.internal.R.id.progress);
if (progress instanceof ProgressBar) {
mProgressBar = (ProgressBar) progress;
// It's still a viewstub
mProgressBar = null;
}
+ }
+
+ @Override
+ public void notifyContentUpdated(StatusBarNotification notification) {
+ // Reinspect the notification.
+ resolveTemplateViews(notification);
+ super.notifyContentUpdated(notification);
+ addRemainingTransformTypes();
+ }
+
+ /**
+ * Adds the remaining TransformTypes to the TransformHelper. This is done to make sure that each
+ * child is faded automatically and doesn't have to be manually added.
+ * The keys used for the views are the ids.
+ */
+ private void addRemainingTransformTypes() {
+
+ }
+
+ @Override
+ protected void updateInvertHelper() {
+ super.updateInvertHelper();
+ View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
if (mainColumn != null) {
mInvertHelper.addTarget(mainColumn);
}
}
@Override
- public void notifyContentUpdated() {
- super.notifyContentUpdated();
-
- // Reinspect the notification.
- resolveTemplateViews();
+ protected void updateTransformedTypes() {
+ // This also clears the existing types
+ super.updateTransformedTypes();
+ if (mTitle != null) {
+ mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TITLE, mTitle);
+ }
+ if (mText != null) {
+ mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TEXT, mText);
+ }
+ if (mPicture != null) {
+ mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_IMAGE, mPicture);
+ }
+ if (mProgressBar != null) {
+ mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_PROGRESS, mProgressBar);
+ }
}
@Override
package com.android.systemui.statusbar;
import android.content.Context;
+import android.service.notification.StatusBarNotification;
import android.view.NotificationHeaderView;
import android.view.View;
+import com.android.systemui.statusbar.notification.TransformState;
+
/**
* Wraps the actual notification content view; used to implement behaviors which are different for
* the individual templates and custom views.
*/
-public abstract class NotificationViewWrapper {
+public abstract class NotificationViewWrapper implements TransformableView {
protected final View mView;
/**
* Notifies this wrapper that the content of the view might have changed.
+ * @param notification
*/
- public void notifyContentUpdated() {};
+ public void notifyContentUpdated(StatusBarNotification notification) {};
/**
* @return true if this template might need to be clipped with a round rect to make it look
public NotificationHeaderView getNotificationHeader() {
return null;
}
+
+ @Override
+ public TransformState getCurrentState(int fadingView) {
+ return null;
+ }
+
+ @Override
+ public void transformTo(TransformableView notification, Runnable endRunnable) {
+ // By default we are fading out completely
+ CrossFadeHelper.fadeOut(mView, endRunnable);
+ }
+
+ @Override
+ public void transformFrom(TransformableView notification) {
+ // By default we are fading in completely
+ CrossFadeHelper.fadeIn(mView);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
}
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import com.android.systemui.statusbar.notification.TransformState;
+
+/**
+ * A view that can be transformed to and from.
+ */
+public interface TransformableView {
+ int TRANSFORMING_VIEW_HEADER = 0;
+ int TRANSFORMING_VIEW_TITLE = 1;
+ int TRANSFORMING_VIEW_TEXT = 2;
+ int TRANSFORMING_VIEW_IMAGE = 3;
+ int TRANSFORMING_VIEW_PROGRESS = 4;
+
+ /**
+ * Get the current state of a view in a transform animation
+ * @param fadingView which view we are interested in
+ * @return the current transform state of this viewtype
+ */
+ TransformState getCurrentState(int fadingView);
+
+ /**
+ * Transform to the given view
+ * @param notification the view to transform to
+ */
+ void transformTo(TransformableView notification, Runnable endRunnable);
+
+ /**
+ * Transform to this view from the given view
+ * @param notification the view to transform from
+ */
+ void transformFrom(TransformableView notification);
+
+ /**
+ * Set this view to be fully visible or gone
+ * @param visible
+ */
+ void setVisible(boolean visible);
+}
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.os.Handler;
+import android.util.ArrayMap;
+import android.view.View;
+
+import com.android.systemui.statusbar.notification.TransformState;
+
+/**
+ * A view that can be transformed to and from.
+ */
+public class ViewTransformationHelper implements TransformableView {
+ private final Handler mHandler = new Handler();
+ private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>();
+
+ public void addTransformedView(int key, View transformedView) {
+ mTransformedViews.put(key, transformedView);
+ }
+
+ public void reset() {
+ mTransformedViews.clear();
+ }
+
+ @Override
+ public TransformState getCurrentState(int fadingView) {
+ View view = mTransformedViews.get(fadingView);
+ if (view != null && view.getVisibility() != View.GONE) {
+ return TransformState.createFrom(view);
+ }
+ return null;
+ }
+
+ @Override
+ public void transformTo(TransformableView notification, Runnable endRunnable) {
+ Runnable runnable = endRunnable;
+ for (Integer viewType : mTransformedViews.keySet()) {
+ TransformState ownState = getCurrentState(viewType);
+ if (ownState != null) {
+ TransformState otherState = notification.getCurrentState(viewType);
+ if (otherState != null) {
+ boolean run = ownState.transformViewTo(otherState, runnable);
+ otherState.recycle();
+ if (run) {
+ runnable = null;
+ }
+ } else {
+ // there's no other view available
+ CrossFadeHelper.fadeOut(mTransformedViews.get(viewType), runnable);
+ runnable = null;
+ }
+ ownState.recycle();
+ }
+ }
+ if (runnable != null) {
+ // We need to post, since the visible type is only set after the transformation is
+ // started
+ mHandler.post(runnable);
+ }
+ }
+
+ @Override
+ public void transformFrom(TransformableView notification) {
+ for (Integer viewType : mTransformedViews.keySet()) {
+ TransformState ownState = getCurrentState(viewType);
+ if (ownState != null) {
+ TransformState otherState = notification.getCurrentState(viewType);
+ if (otherState != null) {
+ ownState.transformViewFrom(otherState);
+ otherState.recycle();
+ } else {
+ // There's no other view, lets fade us in
+ // Certain views need to prepare the fade in and make sure its children are
+ // completely visible. An example is the notification header.
+ ownState.prepareFadeIn();
+ CrossFadeHelper.fadeIn(mTransformedViews.get(viewType));
+ }
+ ownState.recycle();
+ }
+ }
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ for (Integer viewType : mTransformedViews.keySet()) {
+ TransformState ownState = getCurrentState(viewType);
+ if (ownState != null) {
+ ownState.setVisible(visible);
+ ownState.recycle();
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.util.Pools;
+import android.view.NotificationHeaderView;
+import android.view.View;
+
+import com.android.systemui.statusbar.CrossFadeHelper;
+
+/**
+ * A transform state of a text view.
+*/
+public class HeaderTransformState extends TransformState {
+
+ private static Pools.SimplePool<HeaderTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+ private View mExpandButton;
+
+ @Override
+ public void initFrom(View view) {
+ super.initFrom(view);
+ if (view instanceof NotificationHeaderView) {
+ NotificationHeaderView header = (NotificationHeaderView) view;
+ mExpandButton = header.getExpandButton();
+ }
+ }
+
+ @Override
+ public boolean transformViewTo(TransformState otherState, Runnable endRunnable) {
+ // if the transforming notification has a header, we have ensured that it looks the same
+ // but the expand button, so lets fade just that one.
+ if (!(mTransformedView instanceof NotificationHeaderView)) {
+ return false;
+ }
+ NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
+ int childCount = header.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View headerChild = header.getChildAt(i);
+ if (headerChild.getVisibility() == View.GONE) {
+ continue;
+ }
+ if (headerChild != mExpandButton) {
+ headerChild.setVisibility(View.INVISIBLE);
+ } else {
+ CrossFadeHelper.fadeOut(mExpandButton, endRunnable);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void transformViewFrom(TransformState otherState) {
+ // if the transforming notification has a header, we have ensured that it looks the same
+ // but the expand button, so lets fade just that one.
+ if (!(mTransformedView instanceof NotificationHeaderView)) {
+ return;
+ }
+ NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
+ header.setVisibility(View.VISIBLE);
+ header.setAlpha(1.0f);
+ int childCount = header.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View headerChild = header.getChildAt(i);
+ if (headerChild.getVisibility() == View.GONE) {
+ continue;
+ }
+ if (headerChild != mExpandButton) {
+ headerChild.setVisibility(View.VISIBLE);
+ } else {
+ CrossFadeHelper.fadeIn(mExpandButton);
+ }
+ }
+ return;
+ }
+
+ public static HeaderTransformState obtain() {
+ HeaderTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new HeaderTransformState();
+ }
+
+ @Override
+ public void recycle() {
+ sInstancePool.release(this);
+ }
+
+ @Override
+ protected void reset() {
+ super.reset();
+ mExpandButton = null;
+ }
+
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ if (!(mTransformedView instanceof NotificationHeaderView)) {
+ return;
+ }
+ NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
+ int childCount = header.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View headerChild = header.getChildAt(i);
+ if (headerChild.getVisibility() == View.GONE) {
+ continue;
+ }
+ headerChild.animate().cancel();
+ headerChild.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ if (headerChild == mExpandButton) {
+ headerChild.setAlpha(visible ? 1.0f : 0.0f);
+ }
+ }
+ }
+
+ @Override
+ public void prepareFadeIn() {
+ super.prepareFadeIn();
+ if (!(mTransformedView instanceof NotificationHeaderView)) {
+ return;
+ }
+ NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
+ int childCount = header.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View headerChild = header.getChildAt(i);
+ if (headerChild.getVisibility() == View.GONE) {
+ continue;
+ }
+ headerChild.animate().cancel();
+ headerChild.setVisibility(View.VISIBLE);
+ headerChild.setAlpha(1.0f);
+ }
+ }
+}
import android.annotation.Nullable;
import android.content.Context;
+import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.AttributeSet;
+import android.view.View;
import android.widget.TextView;
import com.android.keyguard.AlphaOptimizedLinearLayout;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.phone.NotificationPanelView;
+import java.util.ArrayList;
+
/**
* A hybrid view which may contain information about one ore more notifications.
*/
-public class HybridNotificationView extends AlphaOptimizedLinearLayout {
+public class HybridNotificationView extends AlphaOptimizedLinearLayout
+ implements TransformableView {
+
+ private ViewTransformationHelper mTransformationHelper;
protected TextView mTitleView;
protected TextView mTextView;
mTitleView = (TextView) findViewById(R.id.notification_title);
mTextView = (TextView) findViewById(R.id.notification_text);
mInvertHelper = new ViewInvertHelper(this, NotificationPanelView.DOZE_ANIMATION_DURATION);
+ mTransformationHelper = new ViewTransformationHelper();
+ mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TITLE, mTitleView);
+ mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TEXT, mTextView);
}
public void bind(CharSequence title) {
public void bind(CharSequence title, CharSequence text) {
mTitleView.setText(title);
+ if (TextUtils.isEmpty(title)) {
+ mTitleView.setVisibility(GONE);
+ }
mTextView.setText(text);
+ if (TextUtils.isEmpty(text)) {
+ mTextView.setVisibility(GONE);
+ }
requestLayout();
}
public void setDark(boolean dark, boolean fade, long delay) {
mInvertHelper.setInverted(dark, fade, delay);
}
+
+ @Override
+ public TransformState getCurrentState(int fadingView) {
+ return mTransformationHelper.getCurrentState(fadingView);
+ }
+
+ @Override
+ public void transformTo(TransformableView notification, Runnable endRunnable) {
+ mTransformationHelper.transformTo(notification, endRunnable);
+ }
+
+ @Override
+ public void transformFrom(TransformableView notification) {
+ mTransformationHelper.transformFrom(notification);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ mTransformationHelper.setVisible(visible);
+ }
}
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.graphics.drawable.Icon;
+import android.util.Pools;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+/**
+ * A transform state of a image view.
+*/
+public class ImageTransformState extends TransformState {
+
+ public static final int ICON_TAG = R.id.image_icon_tag;
+ private static Pools.SimplePool<ImageTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+ private Icon mIcon;
+
+ @Override
+ public void initFrom(View view) {
+ super.initFrom(view);
+ if (view instanceof ImageView) {
+ mIcon = (Icon) view.getTag(ICON_TAG);
+ }
+ }
+
+ @Override
+ protected boolean sameAs(TransformState otherState) {
+ if (otherState instanceof ImageTransformState) {
+ return mIcon.sameAs(((ImageTransformState) otherState).getIcon());
+ }
+ return super.sameAs(otherState);
+ }
+
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ public static ImageTransformState obtain() {
+ ImageTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new ImageTransformState();
+ }
+
+ @Override
+ public void recycle() {
+ sInstancePool.release(this);
+ }
+
+ @Override
+ protected void reset() {
+ super.reset();
+ mIcon = null;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.util.Pools;
+
+/**
+ * A transform state of a progress view.
+*/
+public class ProgressTransformState extends TransformState {
+
+ private static Pools.SimplePool<ProgressTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+
+ @Override
+ protected boolean sameAs(TransformState otherState) {
+ if (otherState instanceof ProgressTransformState) {
+ return true;
+ }
+ return super.sameAs(otherState);
+ }
+
+ public static ProgressTransformState obtain() {
+ ProgressTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new ProgressTransformState();
+ }
+
+ @Override
+ public void recycle() {
+ sInstancePool.release(this);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.text.TextUtils;
+import android.util.Pools;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * A transform state of a mText view.
+*/
+public class TextViewTransformState extends TransformState {
+
+ private static Pools.SimplePool<TextViewTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+ private CharSequence mText;
+
+ @Override
+ public void initFrom(View view) {
+ super.initFrom(view);
+ if (view instanceof TextView) {
+ TextView txt = (TextView) view;
+ mText = txt.getText();
+ }
+ }
+
+ @Override
+ protected boolean sameAs(TransformState otherState) {
+ if (otherState instanceof TextViewTransformState) {
+ TextViewTransformState otherTvs = (TextViewTransformState) otherState;
+ return TextUtils.equals(otherTvs.mText, mText);
+ }
+ return super.sameAs(otherState);
+ }
+
+ public static TextViewTransformState obtain() {
+ TextViewTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new TextViewTransformState();
+ }
+
+ @Override
+ public void recycle() {
+ sInstancePool.release(this);
+ }
+
+ @Override
+ protected void reset() {
+ super.reset();
+ mText = null;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.util.ArraySet;
+import android.view.NotificationHeaderView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+
+/**
+ * A transform state of a view.
+*/
+public abstract class TransformState {
+
+ private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
+ private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
+ private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
+ protected View mTransformedView;
+ private int[] mOwnPosition = new int[2];
+
+ public void initFrom(View view) {
+ mTransformedView = view;
+ }
+
+ /**
+ * Transforms the {@link #mTransformedView} from the given transformviewstate
+ * @param otherState the state to transform from
+ */
+ public void transformViewFrom(TransformState otherState) {
+ mTransformedView.animate().cancel();
+ if (sameAs(otherState)) {
+ // We have the same content, lets show ourselves
+ mTransformedView.setAlpha(1.0f);
+ mTransformedView.setVisibility(View.VISIBLE);
+ } else {
+ CrossFadeHelper.fadeIn(mTransformedView);
+ }
+
+ // lets animate the positions correctly
+ int[] otherPosition = otherState.getLocationOnScreen();
+ int[] ownStablePosition = getLaidOutLocationOnScreen();
+ mTransformedView.setTranslationX(otherPosition[0] - ownStablePosition[0]);
+ mTransformedView.setTranslationY(otherPosition[1] - ownStablePosition[1]);
+ mTransformedView.animate()
+ .translationX(0)
+ .translationY(0)
+ .setDuration(CrossFadeHelper.ANIMATION_DURATION_LENGTH)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ setClippingDeactivated(false);
+ }
+ });
+ setClippingDeactivated(true);
+ }
+
+ /**
+ * Transforms the {@link #mTransformedView} to the given transformviewstate
+ * @param otherState the state to transform from
+ * @param endRunnable a runnable to run at the end of the animation
+ * @return whether an animation was started
+ */
+ public boolean transformViewTo(TransformState otherState, final Runnable endRunnable) {
+ mTransformedView.animate().cancel();
+ if (sameAs(otherState)) {
+ // We have the same text, lets show ourselfs
+ mTransformedView.setAlpha(0.0f);
+ mTransformedView.setVisibility(View.INVISIBLE);
+ return false;
+ } else {
+ CrossFadeHelper.fadeOut(mTransformedView, endRunnable);
+ }
+ // lets animate the positions correctly
+ int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
+ int[] ownPosition = getLaidOutLocationOnScreen();
+ mTransformedView.animate()
+ .translationX(otherStablePosition[0] - ownPosition[0])
+ .translationY(otherStablePosition[1] - ownPosition[1])
+ .setDuration(CrossFadeHelper.ANIMATION_DURATION_LENGTH)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ setClippingDeactivated(false);
+ }
+ });
+ setClippingDeactivated(true);
+ return true;
+ }
+
+ private void setClippingDeactivated(boolean deactivated) {
+ ViewGroup view = (ViewGroup) mTransformedView.getParent();
+ while (true) {
+ ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET);
+ if (clipSet == null) {
+ clipSet = new ArraySet<>();
+ view.setTag(CLIP_CLIPPING_SET, clipSet);
+ }
+ Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG);
+ if (clipChildren == null) {
+ clipChildren = view.getClipChildren();
+ view.setTag(CLIP_CHILDREN_TAG, clipChildren);
+ }
+ Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING);
+ if (clipToPadding == null) {
+ clipToPadding = view.getClipToPadding();
+ view.setTag(CLIP_TO_PADDING, clipToPadding);
+ }
+ ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
+ ? (ExpandableNotificationRow) view
+ : null;
+ if (!deactivated) {
+ clipSet.remove(mTransformedView);
+ if (clipSet.isEmpty()) {
+ view.setClipChildren(clipChildren);
+ view.setClipToPadding(clipToPadding);
+ view.setTag(CLIP_CLIPPING_SET, null);
+ if (row != null) {
+ row.setClipToActualHeight(true);
+ }
+ }
+ } else {
+ clipSet.add(mTransformedView);
+ view.setClipChildren(false);
+ view.setClipToPadding(false);
+ if (row != null && row.isChildInGroup()) {
+ // We still want to clip to the parent's height
+ row.setClipToActualHeight(false);
+ }
+ }
+ if (row != null && !row.isChildInGroup()) {
+ return;
+ }
+ final ViewParent parent = view.getParent();
+ if (parent instanceof ViewGroup) {
+ view = (ViewGroup) parent;
+ } else {
+ return;
+ }
+ }
+ }
+
+ private int[] getLaidOutLocationOnScreen() {
+ int[] location = getLocationOnScreen();
+ location[0] -= mTransformedView.getTranslationX();
+ location[1] -= mTransformedView.getTranslationY();
+ return location;
+ }
+
+ private int[] getLocationOnScreen() {
+ mTransformedView.getLocationOnScreen(mOwnPosition);
+ return mOwnPosition;
+ }
+
+ protected boolean sameAs(TransformState otherState) {
+ return false;
+ }
+
+ public static TransformState createFrom(View view) {
+ if (view instanceof TextView) {
+ TextViewTransformState result = TextViewTransformState.obtain();
+ result.initFrom(view);
+ return result;
+ }
+ if (view instanceof NotificationHeaderView) {
+ HeaderTransformState result = HeaderTransformState.obtain();
+ result.initFrom(view);
+ return result;
+ }
+ if (view instanceof ImageView) {
+ ImageTransformState result = ImageTransformState.obtain();
+ result.initFrom(view);
+ return result;
+ }
+ if (view instanceof ProgressBar) {
+ ProgressTransformState result = ProgressTransformState.obtain();
+ result.initFrom(view);
+ return result;
+ }
+ return null;
+ }
+
+ public void recycle() {
+ reset();
+ }
+
+ protected void reset() {
+ mTransformedView = null;
+ }
+
+ public void setVisible(boolean visible) {
+ mTransformedView.animate().cancel();
+ mTransformedView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ mTransformedView.setAlpha(visible ? 1.0f : 0.0f);
+ if (visible) {
+ mTransformedView.setTranslationX(0);
+ mTransformedView.setTranslationY(0);
+ }
+ }
+
+ public void prepareFadeIn() {
+ }
+}