| /* |
| * Copyright (C) 2013 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 static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.animation.ObjectAnimator; |
| import android.animation.ValueAnimator.AnimatorUpdateListener; |
| import android.annotation.Nullable; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.content.res.Configuration; |
| import android.graphics.drawable.AnimatedVectorDrawable; |
| import android.graphics.drawable.AnimationDrawable; |
| import android.graphics.drawable.ColorDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.service.notification.StatusBarNotification; |
| import android.util.AttributeSet; |
| import android.util.FloatProperty; |
| import android.util.Property; |
| import android.view.LayoutInflater; |
| import android.view.MotionEvent; |
| import android.view.NotificationHeaderView; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.ViewStub; |
| import android.view.accessibility.AccessibilityEvent; |
| import android.view.accessibility.AccessibilityNodeInfo; |
| import android.widget.Chronometer; |
| import android.widget.FrameLayout; |
| import android.widget.ImageView; |
| import android.widget.RemoteViews; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.logging.nano.MetricsProto.MetricsEvent; |
| import com.android.internal.util.NotificationColorUtil; |
| import com.android.internal.widget.CachingIconView; |
| import com.android.systemui.Dependency; |
| import com.android.systemui.Interpolators; |
| import com.android.systemui.R; |
| import com.android.systemui.classifier.FalsingManager; |
| import com.android.systemui.plugins.PluginListener; |
| import com.android.systemui.plugins.PluginManager; |
| import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; |
| import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; |
| import com.android.systemui.statusbar.NotificationGuts.GutsContent; |
| import com.android.systemui.statusbar.notification.HybridNotificationView; |
| import com.android.systemui.statusbar.notification.NotificationInflater; |
| import com.android.systemui.statusbar.notification.NotificationUtils; |
| import com.android.systemui.statusbar.notification.VisualStabilityManager; |
| import com.android.systemui.statusbar.phone.NotificationGroupManager; |
| import com.android.systemui.statusbar.phone.StatusBar; |
| import com.android.systemui.statusbar.policy.HeadsUpManager; |
| import com.android.systemui.statusbar.stack.AnimationProperties; |
| import com.android.systemui.statusbar.stack.ExpandableViewState; |
| import com.android.systemui.statusbar.stack.NotificationChildrenContainer; |
| import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; |
| import com.android.systemui.statusbar.stack.StackScrollState; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public class ExpandableNotificationRow extends ActivatableNotificationView |
| implements PluginListener<NotificationMenuRowPlugin> { |
| |
| private static final int DEFAULT_DIVIDER_ALPHA = 0x29; |
| private static final int COLORED_DIVIDER_ALPHA = 0x7B; |
| private static final int MENU_VIEW_INDEX = 0; |
| |
| public interface LayoutListener { |
| public void onLayout(); |
| } |
| |
| private LayoutListener mLayoutListener; |
| private boolean mLowPriorityStateUpdated; |
| private final NotificationInflater mNotificationInflater; |
| private int mIconTransformContentShift; |
| private int mIconTransformContentShiftNoIcon; |
| private int mNotificationMinHeightLegacy; |
| private int mMaxHeadsUpHeightLegacy; |
| private int mMaxHeadsUpHeight; |
| private int mMaxHeadsUpHeightIncreased; |
| private int mNotificationMinHeight; |
| private int mNotificationMinHeightLarge; |
| private int mNotificationMaxHeight; |
| private int mNotificationAmbientHeight; |
| private int mIncreasedPaddingBetweenElements; |
| |
| /** Does this row contain layouts that can adapt to row expansion */ |
| private boolean mExpandable; |
| /** Has the user actively changed the expansion state of this row */ |
| private boolean mHasUserChangedExpansion; |
| /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */ |
| private boolean mUserExpanded; |
| |
| /** |
| * Has this notification been expanded while it was pinned |
| */ |
| private boolean mExpandedWhenPinned; |
| /** Is the user touching this row */ |
| private boolean mUserLocked; |
| /** Are we showing the "public" version */ |
| private boolean mShowingPublic; |
| private boolean mSensitive; |
| private boolean mSensitiveHiddenInGeneral; |
| private boolean mShowingPublicInitialized; |
| private boolean mHideSensitiveForIntrinsicHeight; |
| |
| /** |
| * Is this notification expanded by the system. The expansion state can be overridden by the |
| * user expansion. |
| */ |
| private boolean mIsSystemExpanded; |
| |
| /** |
| * Whether the notification is on the keyguard and the expansion is disabled. |
| */ |
| private boolean mOnKeyguard; |
| |
| private Animator mTranslateAnim; |
| private ArrayList<View> mTranslateableViews; |
| private NotificationContentView mPublicLayout; |
| private NotificationContentView mPrivateLayout; |
| private NotificationContentView[] mLayouts; |
| private int mMaxExpandHeight; |
| private int mHeadsUpHeight; |
| private int mNotificationColor; |
| private ExpansionLogger mLogger; |
| private String mLoggingKey; |
| private NotificationGuts mGuts; |
| private NotificationData.Entry mEntry; |
| private StatusBarNotification mStatusBarNotification; |
| private String mAppName; |
| private boolean mIsHeadsUp; |
| private boolean mLastChronometerRunning = true; |
| private ViewStub mChildrenContainerStub; |
| private NotificationGroupManager mGroupManager; |
| private boolean mChildrenExpanded; |
| private boolean mIsSummaryWithChildren; |
| private NotificationChildrenContainer mChildrenContainer; |
| private NotificationMenuRowPlugin mMenuRow; |
| private ViewStub mGutsStub; |
| private boolean mIsSystemChildExpanded; |
| private boolean mIsPinned; |
| private FalsingManager mFalsingManager; |
| private HeadsUpManager mHeadsUpManager; |
| |
| private boolean mJustClicked; |
| private boolean mIconAnimationRunning; |
| private boolean mShowNoBackground; |
| private ExpandableNotificationRow mNotificationParent; |
| private OnExpandClickListener mOnExpandClickListener; |
| private boolean mGroupExpansionChanging; |
| |
| private OnClickListener mExpandClickListener = new OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| if (!mShowingPublic && (!mIsLowPriority || isExpanded()) |
| && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) { |
| mGroupExpansionChanging = true; |
| final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification); |
| boolean nowExpanded = mGroupManager.toggleGroupExpansion(mStatusBarNotification); |
| mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded); |
| MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER, |
| nowExpanded); |
| onExpansionChanged(true /* userAction */, wasExpanded); |
| } else { |
| if (v.isAccessibilityFocused()) { |
| mPrivateLayout.setFocusOnVisibilityChange(); |
| } |
| boolean nowExpanded; |
| if (isPinned()) { |
| nowExpanded = !mExpandedWhenPinned; |
| mExpandedWhenPinned = nowExpanded; |
| } else { |
| nowExpanded = !isExpanded(); |
| setUserExpanded(nowExpanded); |
| } |
| notifyHeightChanged(true); |
| mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded); |
| MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_EXPANDER, |
| nowExpanded); |
| } |
| } |
| }; |
| private boolean mForceUnlocked; |
| private boolean mDismissed; |
| private boolean mKeepInParent; |
| private boolean mRemoved; |
| private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT = |
| new FloatProperty<ExpandableNotificationRow>("translate") { |
| @Override |
| public void setValue(ExpandableNotificationRow object, float value) { |
| object.setTranslation(value); |
| } |
| |
| @Override |
| public Float get(ExpandableNotificationRow object) { |
| return object.getTranslation(); |
| } |
| }; |
| private OnClickListener mOnClickListener; |
| private boolean mHeadsupDisappearRunning; |
| private View mChildAfterViewWhenDismissed; |
| private View mGroupParentWhenDismissed; |
| private boolean mRefocusOnDismiss; |
| private float mContentTransformationAmount; |
| private boolean mIconsVisible = true; |
| private boolean mAboveShelf; |
| private boolean mShowAmbient; |
| private boolean mIsLastChild; |
| private Runnable mOnDismissRunnable; |
| private boolean mIsLowPriority; |
| private boolean mIsColorized; |
| private boolean mUseIncreasedCollapsedHeight; |
| private boolean mUseIncreasedHeadsUpHeight; |
| private float mTranslationWhenRemoved; |
| private boolean mWasChildInGroupWhenRemoved; |
| private int mNotificationColorAmbient; |
| |
| @Override |
| public boolean isGroupExpansionChanging() { |
| if (isChildInGroup()) { |
| return mNotificationParent.isGroupExpansionChanging(); |
| } |
| return mGroupExpansionChanging; |
| } |
| |
| public void setGroupExpansionChanging(boolean changing) { |
| mGroupExpansionChanging = changing; |
| } |
| |
| @Override |
| public void setActualHeightAnimating(boolean animating) { |
| if (mPrivateLayout != null) { |
| mPrivateLayout.setContentHeightAnimating(animating); |
| } |
| } |
| |
| public NotificationContentView getPrivateLayout() { |
| return mPrivateLayout; |
| } |
| |
| public NotificationContentView getPublicLayout() { |
| return mPublicLayout; |
| } |
| |
| public void setIconAnimationRunning(boolean running) { |
| for (NotificationContentView l : mLayouts) { |
| setIconAnimationRunning(running, l); |
| } |
| if (mIsSummaryWithChildren) { |
| setIconAnimationRunningForChild(running, mChildrenContainer.getHeaderView()); |
| setIconAnimationRunningForChild(running, mChildrenContainer.getLowPriorityHeaderView()); |
| List<ExpandableNotificationRow> notificationChildren = |
| mChildrenContainer.getNotificationChildren(); |
| for (int i = 0; i < notificationChildren.size(); i++) { |
| ExpandableNotificationRow child = notificationChildren.get(i); |
| child.setIconAnimationRunning(running); |
| } |
| } |
| mIconAnimationRunning = running; |
| } |
| |
| private void setIconAnimationRunning(boolean running, NotificationContentView layout) { |
| if (layout != null) { |
| View contractedChild = layout.getContractedChild(); |
| View expandedChild = layout.getExpandedChild(); |
| View headsUpChild = layout.getHeadsUpChild(); |
| setIconAnimationRunningForChild(running, contractedChild); |
| setIconAnimationRunningForChild(running, expandedChild); |
| setIconAnimationRunningForChild(running, headsUpChild); |
| } |
| } |
| |
| private void setIconAnimationRunningForChild(boolean running, View child) { |
| if (child != null) { |
| ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon); |
| setIconRunning(icon, running); |
| ImageView rightIcon = (ImageView) child.findViewById( |
| com.android.internal.R.id.right_icon); |
| setIconRunning(rightIcon, running); |
| } |
| } |
| |
| private void setIconRunning(ImageView imageView, boolean running) { |
| if (imageView != null) { |
| Drawable drawable = imageView.getDrawable(); |
| if (drawable instanceof AnimationDrawable) { |
| AnimationDrawable animationDrawable = (AnimationDrawable) drawable; |
| if (running) { |
| animationDrawable.start(); |
| } else { |
| animationDrawable.stop(); |
| } |
| } else if (drawable instanceof AnimatedVectorDrawable) { |
| AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable; |
| if (running) { |
| animationDrawable.start(); |
| } else { |
| animationDrawable.stop(); |
| } |
| } |
| } |
| } |
| |
| public void updateNotification(NotificationData.Entry entry) { |
| mEntry = entry; |
| mStatusBarNotification = entry.notification; |
| mNotificationInflater.inflateNotificationViews(); |
| } |
| |
| public void onNotificationUpdated() { |
| for (NotificationContentView l : mLayouts) { |
| l.onNotificationUpdated(mEntry); |
| } |
| mIsColorized = mStatusBarNotification.getNotification().isColorized(); |
| mShowingPublicInitialized = false; |
| updateNotificationColor(); |
| if (mMenuRow != null) { |
| mMenuRow.onNotificationUpdated(); |
| } |
| if (mIsSummaryWithChildren) { |
| mChildrenContainer.recreateNotificationHeader(mExpandClickListener); |
| mChildrenContainer.onNotificationUpdated(); |
| } |
| if (mIconAnimationRunning) { |
| setIconAnimationRunning(true); |
| } |
| if (mNotificationParent != null) { |
| mNotificationParent.updateChildrenHeaderAppearance(); |
| } |
| onChildrenCountChanged(); |
| // The public layouts expand button is always visible |
| mPublicLayout.updateExpandButtons(true); |
| updateLimits(); |
| updateIconVisibilities(); |
| updateShelfIconColor(); |
| } |
| |
| @VisibleForTesting |
| void updateShelfIconColor() { |
| StatusBarIconView expandedIcon = mEntry.expandedIcon; |
| boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L)); |
| boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon, |
| NotificationColorUtil.getInstance(mContext)); |
| int color = StatusBarIconView.NO_COLOR; |
| if (colorize) { |
| NotificationHeaderView header = getVisibleNotificationHeader(); |
| if (header != null) { |
| color = header.getOriginalIconColor(); |
| } else { |
| color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded(), |
| getBackgroundColorWithoutTint()); |
| } |
| } |
| expandedIcon.setStaticDrawableColor(color); |
| } |
| |
| @Override |
| public boolean isDimmable() { |
| if (!getShowingLayout().isDimmable()) { |
| return false; |
| } |
| return super.isDimmable(); |
| } |
| |
| private void updateLimits() { |
| for (NotificationContentView l : mLayouts) { |
| updateLimitsForView(l); |
| } |
| } |
| |
| private void updateLimitsForView(NotificationContentView layout) { |
| boolean customView = layout.getContractedChild().getId() |
| != com.android.internal.R.id.status_bar_latest_event_content; |
| boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N; |
| int minHeight; |
| if (customView && beforeN && !mIsSummaryWithChildren) { |
| minHeight = mNotificationMinHeightLegacy; |
| } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) { |
| minHeight = mNotificationMinHeightLarge; |
| } else { |
| minHeight = mNotificationMinHeight; |
| } |
| boolean headsUpCustom = layout.getHeadsUpChild() != null && |
| layout.getHeadsUpChild().getId() |
| != com.android.internal.R.id.status_bar_latest_event_content; |
| int headsUpheight; |
| if (headsUpCustom && beforeN) { |
| headsUpheight = mMaxHeadsUpHeightLegacy; |
| } else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) { |
| headsUpheight = mMaxHeadsUpHeightIncreased; |
| } else { |
| headsUpheight = mMaxHeadsUpHeight; |
| } |
| layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight, |
| mNotificationAmbientHeight); |
| } |
| |
| public StatusBarNotification getStatusBarNotification() { |
| return mStatusBarNotification; |
| } |
| |
| public NotificationData.Entry getEntry() { |
| return mEntry; |
| } |
| |
| public boolean isHeadsUp() { |
| return mIsHeadsUp; |
| } |
| |
| public void setHeadsUp(boolean isHeadsUp) { |
| int intrinsicBefore = getIntrinsicHeight(); |
| mIsHeadsUp = isHeadsUp; |
| mPrivateLayout.setHeadsUp(isHeadsUp); |
| if (mIsSummaryWithChildren) { |
| // The overflow might change since we allow more lines as HUN. |
| mChildrenContainer.updateGroupOverflow(); |
| } |
| if (intrinsicBefore != getIntrinsicHeight()) { |
| notifyHeightChanged(false /* needsAnimation */); |
| } |
| if (isHeadsUp) { |
| setAboveShelf(true); |
| } |
| } |
| |
| public void setGroupManager(NotificationGroupManager groupManager) { |
| mGroupManager = groupManager; |
| mPrivateLayout.setGroupManager(groupManager); |
| } |
| |
| public void setRemoteInputController(RemoteInputController r) { |
| mPrivateLayout.setRemoteInputController(r); |
| } |
| |
| public void setAppName(String appName) { |
| mAppName = appName; |
| if (mMenuRow != null && mMenuRow.getMenuView() != null) { |
| mMenuRow.setAppName(mAppName); |
| } |
| } |
| |
| 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); |
| onChildrenCountChanged(); |
| row.setIsChildInGroup(true, this); |
| } |
| |
| public void removeChildNotification(ExpandableNotificationRow row) { |
| if (mChildrenContainer != null) { |
| mChildrenContainer.removeNotification(row); |
| } |
| onChildrenCountChanged(); |
| row.setIsChildInGroup(false, null); |
| } |
| |
| @Override |
| public boolean isChildInGroup() { |
| return mNotificationParent != null; |
| } |
| |
| public ExpandableNotificationRow getNotificationParent() { |
| return mNotificationParent; |
| } |
| |
| /** |
| * @param isChildInGroup Is this notification now in a group |
| * @param parent the new parent notification |
| */ |
| public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {; |
| boolean childInGroup = StatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup; |
| mNotificationParent = childInGroup ? parent : null; |
| mPrivateLayout.setIsChildInGroup(childInGroup); |
| mNotificationInflater.setIsChildInGroup(childInGroup); |
| resetBackgroundAlpha(); |
| updateBackgroundForGroupState(); |
| updateClickAndFocus(); |
| if (mNotificationParent != null) { |
| setOverrideTintColor(NO_COLOR, 0.0f); |
| mNotificationParent.updateBackgroundForGroupState(); |
| } |
| updateIconVisibilities(); |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent event) { |
| if (event.getActionMasked() != MotionEvent.ACTION_DOWN |
| || !isChildInGroup() || isGroupExpanded()) { |
| return super.onTouchEvent(event); |
| } else { |
| return false; |
| } |
| } |
| |
| @Override |
| protected boolean handleSlideBack() { |
| if (mMenuRow != null && mMenuRow.isMenuVisible()) { |
| animateTranslateNotification(0 /* targetLeft */); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| protected boolean shouldHideBackground() { |
| return super.shouldHideBackground() || mShowNoBackground; |
| } |
| |
| @Override |
| public boolean isSummaryWithChildren() { |
| return mIsSummaryWithChildren; |
| } |
| |
| @Override |
| public boolean areChildrenExpanded() { |
| return mChildrenExpanded; |
| } |
| |
| public List<ExpandableNotificationRow> getNotificationChildren() { |
| return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren(); |
| } |
| |
| public int getNumberOfNotificationChildren() { |
| if (mChildrenContainer == null) { |
| return 0; |
| } |
| return mChildrenContainer.getNotificationChildren().size(); |
| } |
| |
| /** |
| * Apply the order given in the list to the children. |
| * |
| * @param childOrder the new list order |
| * @param visualStabilityManager |
| * @param callback the callback to invoked in case it is not allowed |
| * @return whether the list order has changed |
| */ |
| public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder, |
| VisualStabilityManager visualStabilityManager, |
| VisualStabilityManager.Callback callback) { |
| return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder, |
| visualStabilityManager, callback); |
| } |
| |
| public void getChildrenStates(StackScrollState resultState) { |
| if (mIsSummaryWithChildren) { |
| ExpandableViewState parentState = resultState.getViewStateForView(this); |
| mChildrenContainer.getState(resultState, parentState); |
| } |
| } |
| |
| public void applyChildrenState(StackScrollState state) { |
| if (mIsSummaryWithChildren) { |
| mChildrenContainer.applyState(state); |
| } |
| } |
| |
| public void prepareExpansionChanged(StackScrollState state) { |
| if (mIsSummaryWithChildren) { |
| mChildrenContainer.prepareExpansionChanged(state); |
| } |
| } |
| |
| public void startChildAnimation(StackScrollState finalState, AnimationProperties properties) { |
| if (mIsSummaryWithChildren) { |
| mChildrenContainer.startAnimationToState(finalState, properties); |
| } |
| } |
| |
| public ExpandableNotificationRow getViewAtPosition(float y) { |
| if (!mIsSummaryWithChildren || !mChildrenExpanded) { |
| return this; |
| } else { |
| ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y); |
| return view == null ? this : view; |
| } |
| } |
| |
| public NotificationGuts getGuts() { |
| return mGuts; |
| } |
| |
| /** |
| * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this |
| * the notification will be rendered on top of the screen. |
| * |
| * @param pinned whether it is pinned |
| */ |
| public void setPinned(boolean pinned) { |
| int intrinsicHeight = getIntrinsicHeight(); |
| mIsPinned = pinned; |
| if (intrinsicHeight != getIntrinsicHeight()) { |
| notifyHeightChanged(false /* needsAnimation */); |
| } |
| if (pinned) { |
| setIconAnimationRunning(true); |
| mExpandedWhenPinned = false; |
| } else if (mExpandedWhenPinned) { |
| setUserExpanded(true); |
| } |
| setChronometerRunning(mLastChronometerRunning); |
| } |
| |
| public boolean isPinned() { |
| return mIsPinned; |
| } |
| |
| @Override |
| public int getPinnedHeadsUpHeight() { |
| return getPinnedHeadsUpHeight(true /* atLeastMinHeight */); |
| } |
| |
| /** |
| * @param atLeastMinHeight should the value returned be at least the minimum height. |
| * Used to avoid cyclic calls |
| * @return the height of the heads up notification when pinned |
| */ |
| private int getPinnedHeadsUpHeight(boolean atLeastMinHeight) { |
| if (mIsSummaryWithChildren) { |
| return mChildrenContainer.getIntrinsicHeight(); |
| } |
| if(mExpandedWhenPinned) { |
| return Math.max(getMaxExpandHeight(), mHeadsUpHeight); |
| } else if (atLeastMinHeight) { |
| return Math.max(getCollapsedHeight(), mHeadsUpHeight); |
| } else { |
| return mHeadsUpHeight; |
| } |
| } |
| |
| /** |
| * Mark whether this notification was just clicked, i.e. the user has just clicked this |
| * notification in this frame. |
| */ |
| public void setJustClicked(boolean justClicked) { |
| mJustClicked = justClicked; |
| } |
| |
| /** |
| * @return true if this notification has been clicked in this frame, false otherwise |
| */ |
| public boolean wasJustClicked() { |
| return mJustClicked; |
| } |
| |
| public void setChronometerRunning(boolean running) { |
| mLastChronometerRunning = running; |
| setChronometerRunning(running, mPrivateLayout); |
| setChronometerRunning(running, mPublicLayout); |
| if (mChildrenContainer != null) { |
| List<ExpandableNotificationRow> notificationChildren = |
| mChildrenContainer.getNotificationChildren(); |
| for (int i = 0; i < notificationChildren.size(); i++) { |
| ExpandableNotificationRow child = notificationChildren.get(i); |
| child.setChronometerRunning(running); |
| } |
| } |
| } |
| |
| private void setChronometerRunning(boolean running, NotificationContentView layout) { |
| if (layout != null) { |
| running = running || isPinned(); |
| View contractedChild = layout.getContractedChild(); |
| View expandedChild = layout.getExpandedChild(); |
| View headsUpChild = layout.getHeadsUpChild(); |
| setChronometerRunningForChild(running, contractedChild); |
| setChronometerRunningForChild(running, expandedChild); |
| setChronometerRunningForChild(running, headsUpChild); |
| } |
| } |
| |
| private void setChronometerRunningForChild(boolean running, View child) { |
| if (child != null) { |
| View chronometer = child.findViewById(com.android.internal.R.id.chronometer); |
| if (chronometer instanceof Chronometer) { |
| ((Chronometer) chronometer).setStarted(running); |
| } |
| } |
| } |
| |
| public NotificationHeaderView getNotificationHeader() { |
| if (mIsSummaryWithChildren) { |
| return mChildrenContainer.getHeaderView(); |
| } |
| return mPrivateLayout.getNotificationHeader(); |
| } |
| |
| /** |
| * @return the currently visible notification header. This can be different from |
| * {@link #getNotificationHeader()} in case it is a low-priority group. |
| */ |
| public NotificationHeaderView getVisibleNotificationHeader() { |
| if (mIsSummaryWithChildren && !mShowingPublic) { |
| return mChildrenContainer.getVisibleHeader(); |
| } |
| return getShowingLayout().getVisibleNotificationHeader(); |
| } |
| |
| public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) { |
| mOnExpandClickListener = onExpandClickListener; |
| } |
| |
| @Override |
| public void setOnClickListener(@Nullable OnClickListener l) { |
| super.setOnClickListener(l); |
| mOnClickListener = l; |
| updateClickAndFocus(); |
| } |
| |
| private void updateClickAndFocus() { |
| boolean normalChild = !isChildInGroup() || isGroupExpanded(); |
| boolean clickable = mOnClickListener != null && normalChild; |
| if (isFocusable() != normalChild) { |
| setFocusable(normalChild); |
| } |
| if (isClickable() != clickable) { |
| setClickable(clickable); |
| } |
| } |
| |
| public void setHeadsUpManager(HeadsUpManager headsUpManager) { |
| mHeadsUpManager = headsUpManager; |
| } |
| |
| public void setGutsView(MenuItem item) { |
| if (mGuts != null && item.getGutsView() instanceof GutsContent) { |
| ((GutsContent) item.getGutsView()).setGutsParent(mGuts); |
| mGuts.setGutsContent((GutsContent) item.getGutsView()); |
| } |
| } |
| |
| @Override |
| protected void onAttachedToWindow() { |
| super.onAttachedToWindow(); |
| Dependency.get(PluginManager.class).addPluginListener(this, |
| NotificationMenuRowPlugin.class, false /* Allow multiple */); |
| } |
| |
| @Override |
| protected void onDetachedFromWindow() { |
| super.onDetachedFromWindow(); |
| Dependency.get(PluginManager.class).removePluginListener(this); |
| } |
| |
| @Override |
| public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) { |
| boolean existed = mMenuRow.getMenuView() != null; |
| if (existed) { |
| removeView(mMenuRow.getMenuView()); |
| } |
| mMenuRow = plugin; |
| if (mMenuRow.useDefaultMenuItems()) { |
| ArrayList<MenuItem> items = new ArrayList<>(); |
| items.add(NotificationMenuRow.createInfoItem(mContext)); |
| items.add(NotificationMenuRow.createSnoozeItem(mContext)); |
| mMenuRow.setMenuItems(items); |
| } |
| if (existed) { |
| createMenu(); |
| } |
| } |
| |
| @Override |
| public void onPluginDisconnected(NotificationMenuRowPlugin plugin) { |
| boolean existed = mMenuRow.getMenuView() != null; |
| mMenuRow = new NotificationMenuRow(mContext); // Back to default |
| if (existed) { |
| createMenu(); |
| } |
| } |
| |
| public NotificationMenuRowPlugin createMenu() { |
| if (mMenuRow.getMenuView() == null) { |
| mMenuRow.createMenu(this); |
| mMenuRow.setAppName(mAppName); |
| FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, |
| LayoutParams.MATCH_PARENT); |
| addView(mMenuRow.getMenuView(), MENU_VIEW_INDEX, lp); |
| } |
| return mMenuRow; |
| } |
| |
| public NotificationMenuRowPlugin getProvider() { |
| return mMenuRow; |
| } |
| |
| public void onDensityOrFontScaleChanged() { |
| initDimens(); |
| // Let's update our childrencontainer. This is intentionally not guarded with |
| // mIsSummaryWithChildren since we might have had children but not anymore. |
| if (mChildrenContainer != null) { |
| mChildrenContainer.reInflateViews(mExpandClickListener, mEntry.notification); |
| } |
| if (mGuts != null) { |
| View oldGuts = mGuts; |
| int index = indexOfChild(oldGuts); |
| removeView(oldGuts); |
| mGuts = (NotificationGuts) LayoutInflater.from(mContext).inflate( |
| R.layout.notification_guts, this, false); |
| mGuts.setVisibility(oldGuts.getVisibility()); |
| addView(mGuts, index); |
| } |
| View oldMenu = mMenuRow.getMenuView(); |
| if (oldMenu != null) { |
| int menuIndex = indexOfChild(oldMenu); |
| removeView(oldMenu); |
| mMenuRow.createMenu(ExpandableNotificationRow.this); |
| mMenuRow.setAppName(mAppName); |
| addView(mMenuRow.getMenuView(), menuIndex); |
| } |
| for (NotificationContentView l : mLayouts) { |
| l.reInflateViews(); |
| } |
| mNotificationInflater.onDensityOrFontScaleChanged(); |
| onNotificationUpdated(); |
| } |
| |
| @Override |
| public void onConfigurationChanged(Configuration newConfig) { |
| if (mMenuRow.getMenuView() != null) { |
| mMenuRow.onConfigurationChanged(); |
| } |
| } |
| |
| public void setContentBackground(int customBackgroundColor, boolean animate, |
| NotificationContentView notificationContentView) { |
| if (getShowingLayout() == notificationContentView) { |
| setTintColor(customBackgroundColor, animate); |
| } |
| } |
| |
| public void closeRemoteInput() { |
| for (NotificationContentView l : mLayouts) { |
| l.closeRemoteInput(); |
| } |
| } |
| |
| /** |
| * Set by how much the single line view should be indented. |
| */ |
| public void setSingleLineWidthIndention(int indention) { |
| mPrivateLayout.setSingleLineWidthIndention(indention); |
| } |
| |
| public int getNotificationColor() { |
| return mNotificationColor; |
| } |
| |
| private void updateNotificationColor() { |
| mNotificationColor = NotificationColorUtil.resolveContrastColor(mContext, |
| getStatusBarNotification().getNotification().color, |
| getBackgroundColorWithoutTint()); |
| mNotificationColorAmbient = NotificationColorUtil.resolveAmbientColor(mContext, |
| getStatusBarNotification().getNotification().color); |
| } |
| |
| public HybridNotificationView getSingleLineView() { |
| return mPrivateLayout.getSingleLineView(); |
| } |
| |
| public HybridNotificationView getAmbientSingleLineView() { |
| return getShowingLayout().getAmbientSingleLineChild(); |
| } |
| |
| public boolean isOnKeyguard() { |
| return mOnKeyguard; |
| } |
| |
| public void removeAllChildren() { |
| List<ExpandableNotificationRow> notificationChildren |
| = mChildrenContainer.getNotificationChildren(); |
| ArrayList<ExpandableNotificationRow> clonedList = new ArrayList<>(notificationChildren); |
| for (int i = 0; i < clonedList.size(); i++) { |
| ExpandableNotificationRow row = clonedList.get(i); |
| if (row.keepInParent()) { |
| continue; |
| } |
| mChildrenContainer.removeNotification(row); |
| row.setIsChildInGroup(false, null); |
| } |
| onChildrenCountChanged(); |
| } |
| |
| public void setForceUnlocked(boolean forceUnlocked) { |
| mForceUnlocked = forceUnlocked; |
| if (mIsSummaryWithChildren) { |
| List<ExpandableNotificationRow> notificationChildren = getNotificationChildren(); |
| for (ExpandableNotificationRow child : notificationChildren) { |
| child.setForceUnlocked(forceUnlocked); |
| } |
| } |
| } |
| |
| public void setDismissed(boolean dismissed, boolean fromAccessibility) { |
| mDismissed = dismissed; |
| mGroupParentWhenDismissed = mNotificationParent; |
| mRefocusOnDismiss = fromAccessibility; |
| mChildAfterViewWhenDismissed = null; |
| if (isChildInGroup()) { |
| List<ExpandableNotificationRow> notificationChildren = |
| mNotificationParent.getNotificationChildren(); |
| int i = notificationChildren.indexOf(this); |
| if (i != -1 && i < notificationChildren.size() - 1) { |
| mChildAfterViewWhenDismissed = notificationChildren.get(i + 1); |
| } |
| } |
| } |
| |
| public boolean isDismissed() { |
| return mDismissed; |
| } |
| |
| public boolean keepInParent() { |
| return mKeepInParent; |
| } |
| |
| public void setKeepInParent(boolean keepInParent) { |
| mKeepInParent = keepInParent; |
| } |
| |
| public boolean isRemoved() { |
| return mRemoved; |
| } |
| |
| public void setRemoved() { |
| mRemoved = true; |
| mTranslationWhenRemoved = getTranslationY(); |
| mWasChildInGroupWhenRemoved = isChildInGroup(); |
| if (isChildInGroup()) { |
| mTranslationWhenRemoved += getNotificationParent().getTranslationY(); |
| } |
| mPrivateLayout.setRemoved(); |
| } |
| |
| public boolean wasChildInGroupWhenRemoved() { |
| return mWasChildInGroupWhenRemoved; |
| } |
| |
| public float getTranslationWhenRemoved() { |
| return mTranslationWhenRemoved; |
| } |
| |
| public NotificationChildrenContainer getChildrenContainer() { |
| return mChildrenContainer; |
| } |
| |
| public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { |
| mHeadsupDisappearRunning = headsUpAnimatingAway; |
| mPrivateLayout.setHeadsUpAnimatingAway(headsUpAnimatingAway); |
| } |
| |
| /** |
| * @return if the view was just heads upped and is now animating away. During such a time the |
| * layout needs to be kept consistent |
| */ |
| public boolean isHeadsUpAnimatingAway() { |
| return mHeadsupDisappearRunning; |
| } |
| |
| public View getChildAfterViewWhenDismissed() { |
| return mChildAfterViewWhenDismissed; |
| } |
| |
| public View getGroupParentWhenDismissed() { |
| return mGroupParentWhenDismissed; |
| } |
| |
| public void performDismiss() { |
| if (mOnDismissRunnable != null) { |
| mOnDismissRunnable.run(); |
| } |
| } |
| |
| public void setOnDismissRunnable(Runnable onDismissRunnable) { |
| mOnDismissRunnable = onDismissRunnable; |
| } |
| |
| public View getNotificationIcon() { |
| NotificationHeaderView notificationHeader = getVisibleNotificationHeader(); |
| if (notificationHeader != null) { |
| return notificationHeader.getIcon(); |
| } |
| return null; |
| } |
| |
| /** |
| * @return whether the notification is currently showing a view with an icon. |
| */ |
| public boolean isShowingIcon() { |
| if (areGutsExposed()) { |
| return false; |
| } |
| return getVisibleNotificationHeader() != null; |
| } |
| |
| /** |
| * Set how much this notification is transformed into an icon. |
| * |
| * @param contentTransformationAmount A value from 0 to 1 indicating how much we are transformed |
| * to the content away |
| * @param isLastChild is this the last child in the list. If true, then the transformation is |
| * different since it's content fades out. |
| */ |
| public void setContentTransformationAmount(float contentTransformationAmount, |
| boolean isLastChild) { |
| boolean changeTransformation = isLastChild != mIsLastChild; |
| changeTransformation |= mContentTransformationAmount != contentTransformationAmount; |
| mIsLastChild = isLastChild; |
| mContentTransformationAmount = contentTransformationAmount; |
| if (changeTransformation) { |
| updateContentTransformation(); |
| } |
| } |
| |
| /** |
| * Set the icons to be visible of this notification. |
| */ |
| public void setIconsVisible(boolean iconsVisible) { |
| if (iconsVisible != mIconsVisible) { |
| mIconsVisible = iconsVisible; |
| updateIconVisibilities(); |
| } |
| } |
| |
| @Override |
| protected void onBelowSpeedBumpChanged() { |
| updateIconVisibilities(); |
| } |
| |
| private void updateContentTransformation() { |
| float contentAlpha; |
| float translationY = -mContentTransformationAmount * mIconTransformContentShift; |
| if (mIsLastChild) { |
| contentAlpha = 1.0f - mContentTransformationAmount; |
| contentAlpha = Math.min(contentAlpha / 0.5f, 1.0f); |
| contentAlpha = Interpolators.ALPHA_OUT.getInterpolation(contentAlpha); |
| translationY *= 0.4f; |
| } else { |
| contentAlpha = 1.0f; |
| } |
| for (NotificationContentView l : mLayouts) { |
| l.setAlpha(contentAlpha); |
| l.setTranslationY(translationY); |
| } |
| if (mChildrenContainer != null) { |
| mChildrenContainer.setAlpha(contentAlpha); |
| mChildrenContainer.setTranslationY(translationY); |
| // TODO: handle children fade out better |
| } |
| } |
| |
| private void updateIconVisibilities() { |
| boolean visible = isChildInGroup() |
| || (isBelowSpeedBump() && !NotificationShelf.SHOW_AMBIENT_ICONS) |
| || mIconsVisible; |
| for (NotificationContentView l : mLayouts) { |
| l.setIconsVisible(visible); |
| } |
| if (mChildrenContainer != null) { |
| mChildrenContainer.setIconsVisible(visible); |
| } |
| } |
| |
| /** |
| * Get the relative top padding of a view relative to this view. This recursively walks up the |
| * hierarchy and does the corresponding measuring. |
| * |
| * @param view the view to the the padding for. The requested view has to be a child of this |
| * notification. |
| * @return the toppadding |
| */ |
| public int getRelativeTopPadding(View view) { |
| int topPadding = 0; |
| while (view.getParent() instanceof ViewGroup) { |
| topPadding += view.getTop(); |
| view = (View) view.getParent(); |
| if (view instanceof ExpandableNotificationRow) { |
| return topPadding; |
| } |
| } |
| return topPadding; |
| } |
| |
| public float getContentTranslation() { |
| return mPrivateLayout.getTranslationY(); |
| } |
| |
| public void setIsLowPriority(boolean isLowPriority) { |
| mIsLowPriority = isLowPriority; |
| mPrivateLayout.setIsLowPriority(isLowPriority); |
| mNotificationInflater.setIsLowPriority(mIsLowPriority); |
| if (mChildrenContainer != null) { |
| mChildrenContainer.setIsLowPriority(isLowPriority); |
| } |
| } |
| |
| |
| public void setLowPriorityStateUpdated(boolean lowPriorityStateUpdated) { |
| mLowPriorityStateUpdated = lowPriorityStateUpdated; |
| } |
| |
| public boolean hasLowPriorityStateUpdated() { |
| return mLowPriorityStateUpdated; |
| } |
| |
| public boolean isLowPriority() { |
| return mIsLowPriority; |
| } |
| |
| public void setUseIncreasedCollapsedHeight(boolean use) { |
| mUseIncreasedCollapsedHeight = use; |
| mNotificationInflater.setUsesIncreasedHeight(use); |
| } |
| |
| public void setUseIncreasedHeadsUpHeight(boolean use) { |
| mUseIncreasedHeadsUpHeight = use; |
| mNotificationInflater.setUsesIncreasedHeadsUpHeight(use); |
| } |
| |
| public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) { |
| mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler); |
| } |
| |
| public void setInflationCallback(InflationCallback callback) { |
| mNotificationInflater.setInflationCallback(callback); |
| } |
| |
| public void setNeedsRedaction(boolean needsRedaction) { |
| mNotificationInflater.setRedactAmbient(needsRedaction); |
| } |
| |
| @VisibleForTesting |
| public NotificationInflater getNotificationInflater() { |
| return mNotificationInflater; |
| } |
| |
| public int getNotificationColorAmbient() { |
| return mNotificationColorAmbient; |
| } |
| |
| public interface ExpansionLogger { |
| public void logNotificationExpansion(String key, boolean userAction, boolean expanded); |
| } |
| |
| public ExpandableNotificationRow(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| mFalsingManager = FalsingManager.getInstance(context); |
| mNotificationInflater = new NotificationInflater(this); |
| mMenuRow = new NotificationMenuRow(mContext); |
| initDimens(); |
| } |
| |
| private void initDimens() { |
| mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy); |
| mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height); |
| mNotificationMinHeightLarge = getFontScaledHeight( |
| R.dimen.notification_min_height_increased); |
| mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height); |
| mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height); |
| mMaxHeadsUpHeightLegacy = getFontScaledHeight( |
| R.dimen.notification_max_heads_up_height_legacy); |
| mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height); |
| mMaxHeadsUpHeightIncreased = getFontScaledHeight( |
| R.dimen.notification_max_heads_up_height_increased); |
| mIncreasedPaddingBetweenElements = getResources() |
| .getDimensionPixelSize(R.dimen.notification_divider_height_increased); |
| mIconTransformContentShiftNoIcon = getResources().getDimensionPixelSize( |
| R.dimen.notification_icon_transform_content_shift); |
| } |
| |
| /** |
| * @param dimenId the dimen to look up |
| * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp |
| */ |
| private int getFontScaledHeight(int dimenId) { |
| int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId); |
| float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity / |
| getResources().getDisplayMetrics().density); |
| return (int) (dimensionPixelSize * factor); |
| } |
| |
| /** |
| * Resets this view so it can be re-used for an updated notification. |
| */ |
| public void reset() { |
| mShowingPublicInitialized = false; |
| onHeightReset(); |
| requestLayout(); |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| super.onFinishInflate(); |
| mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic); |
| mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); |
| mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout}; |
| |
| for (NotificationContentView l : mLayouts) { |
| l.setExpandClickListener(mExpandClickListener); |
| l.setContainingNotification(this); |
| } |
| mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub); |
| mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() { |
| @Override |
| public void onInflate(ViewStub stub, View inflated) { |
| mGuts = (NotificationGuts) inflated; |
| mGuts.setClipTopAmount(getClipTopAmount()); |
| mGuts.setActualHeight(getActualHeight()); |
| mGutsStub = null; |
| } |
| }); |
| 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.setIsLowPriority(mIsLowPriority); |
| mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this); |
| mChildrenContainer.onNotificationUpdated(); |
| mTranslateableViews.add(mChildrenContainer); |
| } |
| }); |
| |
| // Add the views that we translate to reveal the menu |
| mTranslateableViews = new ArrayList<View>(); |
| for (int i = 0; i < getChildCount(); i++) { |
| mTranslateableViews.add(getChildAt(i)); |
| } |
| // Remove views that don't translate |
| mTranslateableViews.remove(mChildrenContainerStub); |
| mTranslateableViews.remove(mGutsStub); |
| } |
| |
| public void resetTranslation() { |
| if (mTranslateAnim != null) { |
| mTranslateAnim.cancel(); |
| } |
| if (mTranslateableViews != null) { |
| for (int i = 0; i < mTranslateableViews.size(); i++) { |
| mTranslateableViews.get(i).setTranslationX(0); |
| } |
| } |
| invalidateOutline(); |
| mMenuRow.resetMenu(); |
| } |
| |
| public void animateTranslateNotification(final float leftTarget) { |
| if (mTranslateAnim != null) { |
| mTranslateAnim.cancel(); |
| } |
| mTranslateAnim = getTranslateViewAnimator(leftTarget, null /* updateListener */); |
| if (mTranslateAnim != null) { |
| mTranslateAnim.start(); |
| } |
| } |
| |
| @Override |
| public void setTranslation(float translationX) { |
| if (areGutsExposed()) { |
| // Don't translate if guts are showing. |
| return; |
| } |
| // Translate the group of views |
| for (int i = 0; i < mTranslateableViews.size(); i++) { |
| if (mTranslateableViews.get(i) != null) { |
| mTranslateableViews.get(i).setTranslationX(translationX); |
| } |
| } |
| invalidateOutline(); |
| if (mMenuRow.getMenuView() != null) { |
| mMenuRow.onTranslationUpdate(translationX); |
| } |
| } |
| |
| @Override |
| public float getTranslation() { |
| if (mTranslateableViews != null && mTranslateableViews.size() > 0) { |
| // All of the views in the list should have same translation, just use first one. |
| return mTranslateableViews.get(0).getTranslationX(); |
| } |
| return 0; |
| } |
| |
| public Animator getTranslateViewAnimator(final float leftTarget, |
| AnimatorUpdateListener listener) { |
| if (mTranslateAnim != null) { |
| mTranslateAnim.cancel(); |
| } |
| if (areGutsExposed()) { |
| // No translation if guts are exposed. |
| return null; |
| } |
| final ObjectAnimator translateAnim = ObjectAnimator.ofFloat(this, TRANSLATE_CONTENT, |
| leftTarget); |
| if (listener != null) { |
| translateAnim.addUpdateListener(listener); |
| } |
| translateAnim.addListener(new AnimatorListenerAdapter() { |
| boolean cancelled = false; |
| |
| @Override |
| public void onAnimationCancel(Animator anim) { |
| cancelled = true; |
| } |
| |
| @Override |
| public void onAnimationEnd(Animator anim) { |
| if (!cancelled && leftTarget == 0) { |
| mMenuRow.resetMenu(); |
| mTranslateAnim = null; |
| } |
| } |
| }); |
| mTranslateAnim = translateAnim; |
| return translateAnim; |
| } |
| |
| public void inflateGuts() { |
| if (mGuts == null) { |
| mGutsStub.inflate(); |
| } |
| } |
| |
| private void updateChildrenVisibility() { |
| mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE |
| : INVISIBLE); |
| if (mChildrenContainer != null) { |
| mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE |
| : INVISIBLE); |
| } |
| // The limits might have changed if the view suddenly became a group or vice versa |
| updateLimits(); |
| } |
| |
| @Override |
| public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { |
| if (super.onRequestSendAccessibilityEventInternal(child, event)) { |
| // Add a record for the entire layout since its content is somehow small. |
| // The event comes from a leaf view that is interacted with. |
| AccessibilityEvent record = AccessibilityEvent.obtain(); |
| onInitializeAccessibilityEvent(record); |
| dispatchPopulateAccessibilityEvent(record); |
| event.appendRecord(record); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public void setDark(boolean dark, boolean fade, long delay) { |
| super.setDark(dark, fade, delay); |
| if (!mIsHeadsUp) { |
| // Only fade the showing view of the pulsing notification. |
| fade = false; |
| } |
| final NotificationContentView showing = getShowingLayout(); |
| if (showing != null) { |
| showing.setDark(dark, fade, delay); |
| } |
| if (mIsSummaryWithChildren) { |
| mChildrenContainer.setDark(dark, fade, delay); |
| } |
| updateShelfIconColor(); |
| } |
| |
| public boolean isExpandable() { |
| if (mIsSummaryWithChildren && !mShowingPublic) { |
| return !mChildrenExpanded; |
| } |
| return mExpandable; |
| } |
| |
| public void setExpandable(boolean expandable) { |
| mExpandable = expandable; |
| mPrivateLayout.updateExpandButtons(isExpandable()); |
| } |
| |
| @Override |
| public void setClipToActualHeight(boolean clipToActualHeight) { |
| super.setClipToActualHeight(clipToActualHeight || isUserLocked()); |
| getShowingLayout().setClipToActualHeight(clipToActualHeight || isUserLocked()); |
| } |
| |
| /** |
| * @return whether the user has changed the expansion state |
| */ |
| public boolean hasUserChangedExpansion() { |
| return mHasUserChangedExpansion; |
| } |
| |
| public boolean isUserExpanded() { |
| return mUserExpanded; |
| } |
| |
| /** |
| * Set this notification to be expanded by the user |
| * |
| * @param userExpanded whether the user wants this notification to be expanded |
| */ |
| public void setUserExpanded(boolean userExpanded) { |
| setUserExpanded(userExpanded, false /* allowChildExpansion */); |
| } |
| |
| /** |
| * Set this notification to be expanded by the user |
| * |
| * @param userExpanded whether the user wants this notification to be expanded |
| * @param allowChildExpansion whether a call to this method allows expanding children |
| */ |
| public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { |
| mFalsingManager.setNotificationExpanded(); |
| if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion |
| && !mChildrenContainer.showingAsLowPriority()) { |
| final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification); |
| mGroupManager.setGroupExpanded(mStatusBarNotification, userExpanded); |
| onExpansionChanged(true /* userAction */, wasExpanded); |
| return; |
| } |
| if (userExpanded && !mExpandable) return; |
| final boolean wasExpanded = isExpanded(); |
| mHasUserChangedExpansion = true; |
| mUserExpanded = userExpanded; |
| onExpansionChanged(true /* userAction */, wasExpanded); |
| } |
| |
| public void resetUserExpansion() { |
| boolean changed = mUserExpanded; |
| mHasUserChangedExpansion = false; |
| mUserExpanded = false; |
| if (changed && mIsSummaryWithChildren) { |
| mChildrenContainer.onExpansionChanged(); |
| } |
| updateShelfIconColor(); |
| } |
| |
| public boolean isUserLocked() { |
| return mUserLocked && !mForceUnlocked; |
| } |
| |
| public void setUserLocked(boolean userLocked) { |
| mUserLocked = userLocked; |
| mPrivateLayout.setUserExpanding(userLocked); |
| // This is intentionally not guarded with mIsSummaryWithChildren since we might have had |
| // children but not anymore. |
| if (mChildrenContainer != null) { |
| mChildrenContainer.setUserLocked(userLocked); |
| if (mIsSummaryWithChildren && (userLocked || !isGroupExpanded())) { |
| updateBackgroundForGroupState(); |
| } |
| } |
| } |
| |
| /** |
| * @return has the system set this notification to be expanded |
| */ |
| public boolean isSystemExpanded() { |
| return mIsSystemExpanded; |
| } |
| |
| /** |
| * Set this notification to be expanded by the system. |
| * |
| * @param expand whether the system wants this notification to be expanded. |
| */ |
| public void setSystemExpanded(boolean expand) { |
| if (expand != mIsSystemExpanded) { |
| final boolean wasExpanded = isExpanded(); |
| mIsSystemExpanded = expand; |
| notifyHeightChanged(false /* needsAnimation */); |
| onExpansionChanged(false /* userAction */, wasExpanded); |
| if (mIsSummaryWithChildren) { |
| mChildrenContainer.updateGroupOverflow(); |
| } |
| } |
| } |
| |
| /** |
| * @param onKeyguard whether to prevent notification expansion |
| */ |
| public void setOnKeyguard(boolean onKeyguard) { |
| if (onKeyguard != mOnKeyguard) { |
| final boolean wasExpanded = isExpanded(); |
| mOnKeyguard = onKeyguard; |
| onExpansionChanged(false /* userAction */, wasExpanded); |
| if (wasExpanded != isExpanded()) { |
| if (mIsSummaryWithChildren) { |
| mChildrenContainer.updateGroupOverflow(); |
| } |
| notifyHeightChanged(false /* needsAnimation */); |
| } |
| } |
| } |
| |
| /** |
| * @return Can the underlying notification be cleared? This can be different from whether the |
| * notification can be dismissed in case notifications are sensitive on the lockscreen. |
| * @see #canViewBeDismissed() |
| */ |
| public boolean isClearable() { |
| if (mStatusBarNotification == null || !mStatusBarNotification.isClearable()) { |
| return false; |
| } |
| if (mIsSummaryWithChildren) { |
| List<ExpandableNotificationRow> notificationChildren = |
| mChildrenContainer.getNotificationChildren(); |
| for (int i = 0; i < notificationChildren.size(); i++) { |
| ExpandableNotificationRow child = notificationChildren.get(i); |
| if (!child.isClearable()) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public int getIntrinsicHeight() { |
| if (isUserLocked()) { |
| return getActualHeight(); |
| } |
| if (mGuts != null && mGuts.isExposed()) { |
| return mGuts.getIntrinsicHeight(); |
| } else if ((isChildInGroup() && !isGroupExpanded())) { |
| return mPrivateLayout.getMinHeight(); |
| } else if (mSensitive && mHideSensitiveForIntrinsicHeight) { |
| return getMinHeight(); |
| } else if (mIsSummaryWithChildren && (!mOnKeyguard || mShowAmbient)) { |
| return mChildrenContainer.getIntrinsicHeight(); |
| } else if (isHeadsUpAllowed() && (mIsHeadsUp || mHeadsupDisappearRunning)) { |
| if (isPinned() || mHeadsupDisappearRunning) { |
| return getPinnedHeadsUpHeight(true /* atLeastMinHeight */); |
| } else if (isExpanded()) { |
| return Math.max(getMaxExpandHeight(), mHeadsUpHeight); |
| } else { |
| return Math.max(getCollapsedHeight(), mHeadsUpHeight); |
| } |
| } else if (isExpanded()) { |
| return getMaxExpandHeight(); |
| } else { |
| return getCollapsedHeight(); |
| } |
| } |
| |
| private boolean isHeadsUpAllowed() { |
| return !mOnKeyguard && !mShowAmbient; |
| } |
| |
| @Override |
| public boolean isGroupExpanded() { |
| return mGroupManager.isGroupExpanded(mStatusBarNotification); |
| } |
| |
| private void onChildrenCountChanged() { |
| mIsSummaryWithChildren = StatusBar.ENABLE_CHILD_NOTIFICATIONS |
| && mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0; |
| if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) { |
| mChildrenContainer.recreateNotificationHeader(mExpandClickListener |
| ); |
| } |
| getShowingLayout().updateBackgroundColor(false /* animate */); |
| mPrivateLayout.updateExpandButtons(isExpandable()); |
| updateChildrenHeaderAppearance(); |
| updateChildrenVisibility(); |
| } |
| |
| public void updateChildrenHeaderAppearance() { |
| if (mIsSummaryWithChildren) { |
| mChildrenContainer.updateChildrenHeaderAppearance(); |
| } |
| } |
| |
| /** |
| * Check whether the view state is currently expanded. This is given by the system in {@link |
| * #setSystemExpanded(boolean)} and can be overridden by user expansion or |
| * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this |
| * view can differ from this state, if layout params are modified from outside. |
| * |
| * @return whether the view state is currently expanded. |
| */ |
| public boolean isExpanded() { |
| return isExpanded(false /* allowOnKeyguard */); |
| } |
| |
| public boolean isExpanded(boolean allowOnKeyguard) { |
| return (!mOnKeyguard || allowOnKeyguard) |
| && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded()) |
| || isUserExpanded()); |
| } |
| |
| private boolean isSystemChildExpanded() { |
| return mIsSystemChildExpanded; |
| } |
| |
| public void setSystemChildExpanded(boolean expanded) { |
| mIsSystemChildExpanded = expanded; |
| } |
| |
| public void setLayoutListener(LayoutListener listener) { |
| mLayoutListener = listener; |
| } |
| |
| public void removeListener() { |
| mLayoutListener = null; |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
| super.onLayout(changed, left, top, right, bottom); |
| updateMaxHeights(); |
| if (mMenuRow.getMenuView() != null) { |
| mMenuRow.onHeightUpdate(); |
| } |
| updateContentShiftHeight(); |
| if (mLayoutListener != null) { |
| mLayoutListener.onLayout(); |
| } |
| } |
| |
| /** |
| * Updates the content shift height such that the header is completely hidden when coming from |
| * the top. |
| */ |
| private void updateContentShiftHeight() { |
| NotificationHeaderView notificationHeader = getVisibleNotificationHeader(); |
| if (notificationHeader != null) { |
| CachingIconView icon = notificationHeader.getIcon(); |
| mIconTransformContentShift = getRelativeTopPadding(icon) + icon.getHeight(); |
| } else { |
| mIconTransformContentShift = mIconTransformContentShiftNoIcon; |
| } |
| } |
| |
| private void updateMaxHeights() { |
| int intrinsicBefore = getIntrinsicHeight(); |
| View expandedChild = mPrivateLayout.getExpandedChild(); |
| if (expandedChild == null) { |
| expandedChild = mPrivateLayout.getContractedChild(); |
| } |
| mMaxExpandHeight = expandedChild.getHeight(); |
| View headsUpChild = mPrivateLayout.getHeadsUpChild(); |
| if (headsUpChild == null) { |
| headsUpChild = mPrivateLayout.getContractedChild(); |
| } |
| mHeadsUpHeight = headsUpChild.getHeight(); |
| if (intrinsicBefore != getIntrinsicHeight()) { |
| notifyHeightChanged(true /* needsAnimation */); |
| } |
| } |
| |
| @Override |
| public void notifyHeightChanged(boolean needsAnimation) { |
| super.notifyHeightChanged(needsAnimation); |
| getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked()); |
| } |
| |
| public void setSensitive(boolean sensitive, boolean hideSensitive) { |
| mSensitive = sensitive; |
| mSensitiveHiddenInGeneral = hideSensitive; |
| } |
| |
| @Override |
| public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) { |
| mHideSensitiveForIntrinsicHeight = hideSensitive; |
| if (mIsSummaryWithChildren) { |
| List<ExpandableNotificationRow> notificationChildren = |
| mChildrenContainer.getNotificationChildren(); |
| for (int i = 0; i < notificationChildren.size(); i++) { |
| ExpandableNotificationRow child = notificationChildren.get(i); |
| child.setHideSensitiveForIntrinsicHeight(hideSensitive); |
| } |
| } |
| } |
| |
| @Override |
| public void setHideSensitive(boolean hideSensitive, boolean animated, long delay, |
| long duration) { |
| boolean oldShowingPublic = mShowingPublic; |
| mShowingPublic = mSensitive && hideSensitive; |
| if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) { |
| return; |
| } |
| |
| // bail out if no public version |
| if (mPublicLayout.getChildCount() == 0) return; |
| |
| if (!animated) { |
| mPublicLayout.animate().cancel(); |
| mPrivateLayout.animate().cancel(); |
| if (mChildrenContainer != null) { |
| mChildrenContainer.animate().cancel(); |
| mChildrenContainer.setAlpha(1f); |
| } |
| mPublicLayout.setAlpha(1f); |
| mPrivateLayout.setAlpha(1f); |
| mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); |
| updateChildrenVisibility(); |
| } else { |
| animateShowingPublic(delay, duration); |
| } |
| NotificationContentView showingLayout = getShowingLayout(); |
| showingLayout.updateBackgroundColor(animated); |
| mPrivateLayout.updateExpandButtons(isExpandable()); |
| updateShelfIconColor(); |
| showingLayout.setDark(isDark(), false /* animate */, 0 /* delay */); |
| mShowingPublicInitialized = true; |
| } |
| |
| private void animateShowingPublic(long delay, long duration) { |
| View[] privateViews = mIsSummaryWithChildren |
| ? new View[] {mChildrenContainer} |
| : new View[] {mPrivateLayout}; |
| View[] publicViews = new View[] {mPublicLayout}; |
| View[] hiddenChildren = mShowingPublic ? privateViews : publicViews; |
| View[] shownChildren = mShowingPublic ? publicViews : privateViews; |
| for (final View hiddenView : hiddenChildren) { |
| hiddenView.setVisibility(View.VISIBLE); |
| hiddenView.animate().cancel(); |
| hiddenView.animate() |
| .alpha(0f) |
| .setStartDelay(delay) |
| .setDuration(duration) |
| .withEndAction(new Runnable() { |
| @Override |
| public void run() { |
| hiddenView.setVisibility(View.INVISIBLE); |
| } |
| }); |
| } |
| for (View showView : shownChildren) { |
| showView.setVisibility(View.VISIBLE); |
| showView.setAlpha(0f); |
| showView.animate().cancel(); |
| showView.animate() |
| .alpha(1f) |
| .setStartDelay(delay) |
| .setDuration(duration); |
| } |
| } |
| |
| @Override |
| public boolean mustStayOnScreen() { |
| return mIsHeadsUp; |
| } |
| |
| /** |
| * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as |
| * otherwise some state might not be updated. To request about the general clearability |
| * see {@link #isClearable()}. |
| */ |
| public boolean canViewBeDismissed() { |
| return isClearable() && (!mShowingPublic || !mSensitiveHiddenInGeneral); |
| } |
| |
| public void makeActionsVisibile() { |
| setUserExpanded(true, true); |
| if (isChildInGroup()) { |
| mGroupManager.setGroupExpanded(mStatusBarNotification, true); |
| } |
| notifyHeightChanged(false /* needsAnimation */); |
| } |
| |
| public void setChildrenExpanded(boolean expanded, boolean animate) { |
| mChildrenExpanded = expanded; |
| if (mChildrenContainer != null) { |
| mChildrenContainer.setChildrenExpanded(expanded); |
| } |
| updateBackgroundForGroupState(); |
| updateClickAndFocus(); |
| } |
| |
| 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 mMaxExpandHeight; |
| } |
| |
| public boolean areGutsExposed() { |
| return (mGuts != null && mGuts.isExposed()); |
| } |
| |
| @Override |
| public boolean isContentExpandable() { |
| if (mIsSummaryWithChildren && !mShowingPublic) { |
| return true; |
| } |
| NotificationContentView showingLayout = getShowingLayout(); |
| return showingLayout.isContentExpandable(); |
| } |
| |
| @Override |
| protected View getContentView() { |
| if (mIsSummaryWithChildren && !mShowingPublic) { |
| return mChildrenContainer; |
| } |
| return getShowingLayout(); |
| } |
| |
| @Override |
| protected void onAppearAnimationFinished(boolean wasAppearing) { |
| super.onAppearAnimationFinished(wasAppearing); |
| if (wasAppearing) { |
| // During the animation the visible view might have changed, so let's make sure all |
| // alphas are reset |
| if (mChildrenContainer != null) { |
| mChildrenContainer.setAlpha(1.0f); |
| mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null); |
| } |
| for (NotificationContentView l : mLayouts) { |
| l.setAlpha(1.0f); |
| l.setLayerType(LAYER_TYPE_NONE, null); |
| } |
| } |
| } |
| |
| @Override |
| public int getExtraBottomPadding() { |
| if (mIsSummaryWithChildren && isGroupExpanded()) { |
| return mIncreasedPaddingBetweenElements; |
| } |
| return 0; |
| } |
| |
| @Override |
| public void setActualHeight(int height, boolean notifyListeners) { |
| boolean changed = height != getActualHeight(); |
| super.setActualHeight(height, notifyListeners); |
| if (changed && isRemoved()) { |
| // TODO: remove this once we found the gfx bug for this. |
| // This is a hack since a removed view sometimes would just stay blank. it occured |
| // when sending yourself a message and then clicking on it. |
| ViewGroup parent = (ViewGroup) getParent(); |
| if (parent != null) { |
| parent.invalidate(); |
| } |
| } |
| if (mGuts != null && mGuts.isExposed()) { |
| mGuts.setActualHeight(height); |
| return; |
| } |
| int contentHeight = Math.max(getMinHeight(), height); |
| for (NotificationContentView l : mLayouts) { |
| l.setContentHeight(contentHeight); |
| } |
| if (mIsSummaryWithChildren) { |
| mChildrenContainer.setActualHeight(height); |
| } |
| if (mGuts != null) { |
| mGuts.setActualHeight(height); |
| } |
| } |
| |
| @Override |
| public int getMaxContentHeight() { |
| if (mIsSummaryWithChildren && !mShowingPublic) { |
| return mChildrenContainer.getMaxContentHeight(); |
| } |
| NotificationContentView showingLayout = getShowingLayout(); |
| return showingLayout.getMaxHeight(); |
| } |
| |
| @Override |
| public int getMinHeight() { |
| if (mGuts != null && mGuts.isExposed()) { |
| return mGuts.getIntrinsicHeight(); |
| } else if (isHeadsUpAllowed() && mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) { |
| return getPinnedHeadsUpHeight(false /* atLeastMinHeight */); |
| } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) { |
| return mChildrenContainer.getMinHeight(); |
| } else if (isHeadsUpAllowed() && mIsHeadsUp) { |
| return mHeadsUpHeight; |
| } |
| NotificationContentView showingLayout = getShowingLayout(); |
| return showingLayout.getMinHeight(); |
| } |
| |
| @Override |
| public int getCollapsedHeight() { |
| if (mIsSummaryWithChildren && !mShowingPublic) { |
| return mChildrenContainer.getCollapsedHeight(); |
| } |
| return getMinHeight(); |
| } |
| |
| @Override |
| public void setClipTopAmount(int clipTopAmount) { |
| super.setClipTopAmount(clipTopAmount); |
| for (NotificationContentView l : mLayouts) { |
| l.setClipTopAmount(clipTopAmount); |
| } |
| if (mGuts != null) { |
| mGuts.setClipTopAmount(clipTopAmount); |
| } |
| } |
| |
| @Override |
| public void setClipBottomAmount(int clipBottomAmount) { |
| if (clipBottomAmount != mClipBottomAmount) { |
| super.setClipBottomAmount(clipBottomAmount); |
| for (NotificationContentView l : mLayouts) { |
| l.setClipBottomAmount(clipBottomAmount); |
| } |
| if (mGuts != null) { |
| mGuts.setClipBottomAmount(clipBottomAmount); |
| } |
| } |
| if (mChildrenContainer != null) { |
| // We have to update this even if it hasn't changed, since the children locations can |
| // have changed |
| mChildrenContainer.setClipBottomAmount(clipBottomAmount); |
| } |
| } |
| |
| public boolean isMaxExpandHeightInitialized() { |
| return mMaxExpandHeight != 0; |
| } |
| |
| public NotificationContentView getShowingLayout() { |
| return mShowingPublic ? mPublicLayout : mPrivateLayout; |
| } |
| |
| public void setLegacy(boolean legacy) { |
| for (NotificationContentView l : mLayouts) { |
| l.setLegacy(legacy); |
| } |
| } |
| |
| @Override |
| protected void updateBackgroundTint() { |
| super.updateBackgroundTint(); |
| updateBackgroundForGroupState(); |
| if (mIsSummaryWithChildren) { |
| List<ExpandableNotificationRow> notificationChildren = |
| mChildrenContainer.getNotificationChildren(); |
| for (int i = 0; i < notificationChildren.size(); i++) { |
| ExpandableNotificationRow child = notificationChildren.get(i); |
| child.updateBackgroundForGroupState(); |
| } |
| } |
| } |
| |
| /** |
| * Called when a group has finished animating from collapsed or expanded state. |
| */ |
| public void onFinishedExpansionChange() { |
| mGroupExpansionChanging = false; |
| updateBackgroundForGroupState(); |
| } |
| |
| /** |
| * Updates the parent and children backgrounds in a group based on the expansion state. |
| */ |
| public void updateBackgroundForGroupState() { |
| if (mIsSummaryWithChildren) { |
| // Only when the group has finished expanding do we hide its background. |
| mShowNoBackground = isGroupExpanded() && !isGroupExpansionChanging() && !isUserLocked(); |
| mChildrenContainer.updateHeaderForExpansion(mShowNoBackground); |
| List<ExpandableNotificationRow> children = mChildrenContainer.getNotificationChildren(); |
| for (int i = 0; i < children.size(); i++) { |
| children.get(i).updateBackgroundForGroupState(); |
| } |
| } else if (isChildInGroup()) { |
| final int childColor = getShowingLayout().getBackgroundColorForExpansionState(); |
| // Only show a background if the group is expanded OR if it is expanding / collapsing |
| // and has a custom background color |
| final boolean showBackground = isGroupExpanded() |
| || ((mNotificationParent.isGroupExpansionChanging() |
| || mNotificationParent.isUserLocked()) && childColor != 0); |
| mShowNoBackground = !showBackground; |
| } else { |
| // Only children or parents ever need no background. |
| mShowNoBackground = false; |
| } |
| updateOutline(); |
| updateBackground(); |
| } |
| |
| public int getPositionOfChild(ExpandableNotificationRow childRow) { |
| if (mIsSummaryWithChildren) { |
| return mChildrenContainer.getPositionInLinearLayout(childRow); |
| } |
| return 0; |
| } |
| |
| public void setExpansionLogger(ExpansionLogger logger, String key) { |
| mLogger = logger; |
| mLoggingKey = key; |
| } |
| |
| public void onExpandedByGesture(boolean userExpanded) { |
| int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER; |
| if (mGroupManager.isSummaryOfGroup(getStatusBarNotification())) { |
| event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER; |
| } |
| MetricsLogger.action(mContext, event, userExpanded); |
| } |
| |
| @Override |
| public float getIncreasedPaddingAmount() { |
| if (mIsSummaryWithChildren) { |
| if (isGroupExpanded()) { |
| return 1.0f; |
| } else if (isUserLocked()) { |
| return mChildrenContainer.getIncreasedPaddingAmount(); |
| } |
| } else if (isColorized() && (!mIsLowPriority || isExpanded())) { |
| return -1.0f; |
| } |
| return 0.0f; |
| } |
| |
| private boolean isColorized() { |
| return mIsColorized && mBgTint != NO_COLOR; |
| } |
| |
| @Override |
| protected boolean disallowSingleClick(MotionEvent event) { |
| float x = event.getX(); |
| float y = event.getY(); |
| NotificationHeaderView header = getVisibleNotificationHeader(); |
| if (header != null) { |
| return header.isInTouchRect(x - getTranslation(), y); |
| } |
| return super.disallowSingleClick(event); |
| } |
| |
| private void onExpansionChanged(boolean userAction, boolean wasExpanded) { |
| boolean nowExpanded = isExpanded(); |
| if (mIsSummaryWithChildren && (!mIsLowPriority || wasExpanded)) { |
| nowExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification); |
| } |
| if (nowExpanded != wasExpanded) { |
| updateShelfIconColor(); |
| if (mLogger != null) { |
| mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded); |
| } |
| if (mIsSummaryWithChildren) { |
| mChildrenContainer.onExpansionChanged(); |
| } |
| } |
| } |
| |
| @Override |
| public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { |
| super.onInitializeAccessibilityNodeInfoInternal(info); |
| if (canViewBeDismissed()) { |
| info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); |
| } |
| boolean expandable = mShowingPublic; |
| boolean isExpanded = false; |
| if (!expandable) { |
| if (mIsSummaryWithChildren) { |
| expandable = true; |
| if (!mIsLowPriority || isExpanded()) { |
| isExpanded = isGroupExpanded(); |
| } |
| } else { |
| expandable = mPrivateLayout.isContentExpandable(); |
| isExpanded = isExpanded(); |
| } |
| } |
| if (expandable) { |
| if (isExpanded) { |
| info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE); |
| } else { |
| info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); |
| } |
| } |
| } |
| |
| @Override |
| public boolean performAccessibilityActionInternal(int action, Bundle arguments) { |
| if (super.performAccessibilityActionInternal(action, arguments)) { |
| return true; |
| } |
| switch (action) { |
| case AccessibilityNodeInfo.ACTION_DISMISS: |
| NotificationStackScrollLayout.performDismiss(this, mGroupManager, |
| true /* fromAccessibility */); |
| return true; |
| case AccessibilityNodeInfo.ACTION_COLLAPSE: |
| case AccessibilityNodeInfo.ACTION_EXPAND: |
| mExpandClickListener.onClick(this); |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean shouldRefocusOnDismiss() { |
| return mRefocusOnDismiss || isAccessibilityFocused(); |
| } |
| |
| public interface OnExpandClickListener { |
| void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded); |
| } |
| |
| @Override |
| public ExpandableViewState createNewViewState(StackScrollState stackScrollState) { |
| return new NotificationViewState(stackScrollState); |
| } |
| |
| @Override |
| public boolean isAboveShelf() { |
| return !isOnKeyguard() |
| && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf)); |
| } |
| |
| public void setShowAmbient(boolean showAmbient) { |
| if (showAmbient != mShowAmbient) { |
| mShowAmbient = showAmbient; |
| if (mChildrenContainer != null) { |
| mChildrenContainer.notifyShowAmbientChanged(); |
| } |
| notifyHeightChanged(false /* needsAnimation */); |
| } |
| } |
| |
| public boolean isShowingAmbient() { |
| return mShowAmbient; |
| } |
| |
| public void setAboveShelf(boolean aboveShelf) { |
| mAboveShelf = aboveShelf; |
| } |
| |
| public static class NotificationViewState extends ExpandableViewState { |
| |
| private final StackScrollState mOverallState; |
| |
| |
| private NotificationViewState(StackScrollState stackScrollState) { |
| mOverallState = stackScrollState; |
| } |
| |
| @Override |
| public void applyToView(View view) { |
| super.applyToView(view); |
| if (view instanceof ExpandableNotificationRow) { |
| ExpandableNotificationRow row = (ExpandableNotificationRow) view; |
| row.applyChildrenState(mOverallState); |
| } |
| } |
| |
| @Override |
| protected void onYTranslationAnimationFinished(View view) { |
| super.onYTranslationAnimationFinished(view); |
| if (view instanceof ExpandableNotificationRow) { |
| ExpandableNotificationRow row = (ExpandableNotificationRow) view; |
| if (row.isHeadsUpAnimatingAway()) { |
| row.setHeadsUpAnimatingAway(false); |
| } |
| } |
| } |
| |
| @Override |
| public void animateTo(View child, AnimationProperties properties) { |
| super.animateTo(child, properties); |
| if (child instanceof ExpandableNotificationRow) { |
| ExpandableNotificationRow row = (ExpandableNotificationRow) child; |
| row.startChildAnimation(mOverallState, properties); |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) { |
| mChildrenContainer = childrenContainer; |
| } |
| } |