Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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.notification.row;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_AMBIENT;
import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_HEADS_UP;
import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_PUBLIC;
import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationCallback;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_AMBIENT;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Path;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
import android.util.MathUtils;
import android.util.Property;
import android.view.KeyEvent;
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.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
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.ContrastColorUtil;
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.FalsingManagerFactory;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
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.policy.InflatedSmartReplies.SmartRepliesAndActions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
/**
* View representing a notification item - this can be either the individual child notification or
* the group summary (which contains 1 or more child notifications).
*/
public class ExpandableNotificationRow extends ActivatableNotificationView
implements PluginListener {
private static final boolean DEBUG = false;
private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
private static final int COLORED_DIVIDER_ALPHA = 0x7B;
private static final int MENU_VIEW_INDEX = 0;
private static final String TAG = "ExpandableNotifRow";
public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
private boolean mUpdateBackgroundOnUpdate;
private boolean mNotificationTranslationFinished = false;
/**
* Listener for when {@link ExpandableNotificationRow} is laid out.
*/
public interface LayoutListener {
void onLayout();
}
private LayoutListener mLayoutListener;
private final NotificationContentInflater mNotificationInflater;
private int mIconTransformContentShift;
private int mIconTransformContentShiftNoIcon;
private int mMaxHeadsUpHeightBeforeN;
private int mMaxHeadsUpHeightBeforeP;
private int mMaxHeadsUpHeight;
private int mMaxHeadsUpHeightIncreased;
private int mNotificationMinHeightBeforeN;
private int mNotificationMinHeightBeforeP;
private int mNotificationMinHeight;
private int mNotificationMinHeightLarge;
private int mNotificationMinHeightMedia;
private int mNotificationMaxHeight;
private int mIncreasedPaddingBetweenElements;
private int mNotificationLaunchHeight;
private boolean mMustStayOnScreen;
/** 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;
/** Whether the blocking helper is showing on this notification (even if dismissed) */
private boolean mIsBlockingHelperShowing;
/**
* 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;
private float mHeaderVisibleAmount = DEFAULT_HEADER_VISIBLE_AMOUNT;
/**
* 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;
/**
* Whether or not the row is currently on the doze screen.
*/
private boolean mOnAmbient;
private Animator mTranslateAnim;
private ArrayList mTranslateableViews;
private NotificationContentView mPublicLayout;
private NotificationContentView mPrivateLayout;
private NotificationContentView[] mLayouts;
private int mNotificationColor;
private ExpansionLogger mLogger;
private String mLoggingKey;
private NotificationGuts mGuts;
private NotificationEntry mEntry;
private StatusBarNotification mStatusBarNotification;
private String mAppName;
/**
* Whether or not the notification is using the heads up view and should peek from the top.
*/
private boolean mIsHeadsUp;
/**
* Whether or not the notification is using the ambient display view and is pulsing. This
* occurs when a high priority notification alerts while the phone is dozing or is on AOD.
*/
private boolean mIsAmbientPulsing;
/**
* Happens when the notification was pulsing before and goes away to ensure smooth animations.
*/
private boolean mAmbientGoingAway;
/**
* Whether or not the notification should be redacted on the lock screen, i.e has sensitive
* content which should be redacted on the lock screen.
*/
private boolean mNeedsRedaction;
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 boolean mExpandAnimationRunning;
private AboveShelfChangedListener mAboveShelfChangedListener;
private HeadsUpManager mHeadsUpManager;
private Consumer mHeadsUpAnimatingAwayListener;
private boolean mChildIsExpanding;
private boolean mJustClicked;
private boolean mIconAnimationRunning;
private boolean mShowNoBackground;
private ExpandableNotificationRow mNotificationParent;
private OnExpandClickListener mOnExpandClickListener;
private View.OnClickListener mOnAppOpsClickListener;
// Listener will be called when receiving a long click event.
// Use #setLongPressPosition to optionally assign positional data with the long press.
private LongPressListener mLongPressListener;
private boolean mGroupExpansionChanging;
/**
* A supplier that returns true if keyguard is secure.
*/
private BooleanSupplier mSecureStateProvider;
/**
* Whether or not a notification that is not part of a group of notifications can be manually
* expanded by the user.
*/
private boolean mEnableNonGroupedNotificationExpand;
/**
* Whether or not to update the background of the header of the notification when its expanded.
* If {@code true}, the header background will disappear when expanded.
*/
private boolean mShowGroupBackgroundWhenExpanded;
private OnClickListener mExpandClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
if (!shouldShowPublic() && (!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 (mEnableNonGroupedNotificationExpand) {
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 TRANSLATE_CONTENT =
new FloatProperty("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 mIsLastChild;
private Runnable mOnDismissRunnable;
private boolean mIsLowPriority;
private boolean mIsColorized;
private boolean mUseIncreasedCollapsedHeight;
private boolean mUseIncreasedHeadsUpHeight;
private float mTranslationWhenRemoved;
private boolean mWasChildInGroupWhenRemoved;
private NotificationInlineImageResolver mImageResolver;
private NotificationMediaManager mMediaManager;
private SystemNotificationAsyncTask mSystemNotificationAsyncTask =
new SystemNotificationAsyncTask();
private int mStatusBarState = -1;
/**
* Returns whether the given {@code statusBarNotification} is a system notification.
* Note, this should be run in the background thread if possible as it makes multiple IPC
* calls.
*/
private static Boolean isSystemNotification(
Context context, StatusBarNotification statusBarNotification) {
PackageManager packageManager = StatusBar.getPackageManagerForUser(
context, statusBarNotification.getUser().getIdentifier());
Boolean isSystemNotification = null;
try {
PackageInfo packageInfo = packageManager.getPackageInfo(
statusBarNotification.getPackageName(), PackageManager.GET_SIGNATURES);
isSystemNotification =
com.android.settingslib.Utils.isSystemPackage(
context.getResources(), packageManager, packageInfo);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "cacheIsSystemNotification: Could not find package info");
}
return isSystemNotification;
}
@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 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();
}
}
}
}
/**
* Set the entry for the row.
*
* @param entry the entry this row is tied to
*/
public void setEntry(@NonNull NotificationEntry entry) {
mEntry = entry;
mStatusBarNotification = entry.notification;
cacheIsSystemNotification();
}
/**
* Inflate views based off the inflation flags set. Inflation happens asynchronously.
*/
public void inflateViews() {
mNotificationInflater.inflateNotificationViews();
}
/**
* Marks a content view as freeable, setting it so that future inflations do not reinflate
* and ensuring that the view is freed when it is safe to remove.
*
* @param inflationFlag flag corresponding to the content view to be freed
*/
public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
// View should not be reinflated in the future
updateInflationFlag(inflationFlag, false);
Runnable freeViewRunnable = () ->
mNotificationInflater.freeNotificationView(inflationFlag);
switch (inflationFlag) {
case FLAG_CONTENT_VIEW_HEADS_UP:
getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP,
freeViewRunnable);
break;
case FLAG_CONTENT_VIEW_AMBIENT:
getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_AMBIENT,
freeViewRunnable);
getPublicLayout().performWhenContentInactive(VISIBLE_TYPE_AMBIENT,
freeViewRunnable);
break;
case FLAG_CONTENT_VIEW_PUBLIC:
getPublicLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED,
freeViewRunnable);
default:
break;
}
}
/**
* Update whether or not a content view should be inflated.
*
* @param flag the flag corresponding to the content view
* @param shouldInflate true if it should be inflated, false if it should not
*/
public void updateInflationFlag(@InflationFlag int flag, boolean shouldInflate) {
mNotificationInflater.updateInflationFlag(flag, shouldInflate);
}
/**
* Whether or not a content view should be inflated.
*
* @param flag the flag corresponding to the content view
* @return true if the flag is set, false otherwise
*/
public boolean isInflationFlagSet(@InflationFlag int flag) {
return mNotificationInflater.isInflationFlagSet(flag);
}
/**
* Caches whether or not this row contains a system notification. Note, this is only cached
* once per notification as the packageInfo can't technically change for a notification row.
*/
private void cacheIsSystemNotification() {
if (mEntry != null && mEntry.mIsSystemNotification == null) {
if (mSystemNotificationAsyncTask.getStatus() == AsyncTask.Status.PENDING) {
// Run async task once, only if it hasn't already been executed. Note this is
// executed in serial - no need to parallelize this small task.
mSystemNotificationAsyncTask.execute();
}
}
}
/**
* Returns whether this row is considered non-blockable (i.e. it's a non-blockable system notif
* or is in a whitelist).
*/
public boolean getIsNonblockable() {
boolean isNonblockable = Dependency.get(NotificationBlockingHelperManager.class)
.isNonblockable(mStatusBarNotification.getPackageName(),
mEntry.channel.getId());
// If the SystemNotifAsyncTask hasn't finished running or retrieved a value, we'll try once
// again, but in-place on the main thread this time. This should rarely ever get called.
if (mEntry != null && mEntry.mIsSystemNotification == null) {
if (DEBUG) {
Log.d(TAG, "Retrieving isSystemNotification on main thread");
}
mSystemNotificationAsyncTask.cancel(true /* mayInterruptIfRunning */);
mEntry.mIsSystemNotification = isSystemNotification(mContext, mStatusBarNotification);
}
isNonblockable |= mEntry.channel.isImportanceLockedByOEM();
isNonblockable |= mEntry.channel.isImportanceLockedByCriticalDeviceFunction();
if (!isNonblockable && mEntry != null && mEntry.mIsSystemNotification != null) {
if (mEntry.mIsSystemNotification) {
if (mEntry.channel != null
&& !mEntry.channel.isBlockableSystem()) {
isNonblockable = true;
}
}
}
return isNonblockable;
}
public void onNotificationUpdated() {
for (NotificationContentView l : mLayouts) {
l.onNotificationUpdated(mEntry);
}
mIsColorized = mStatusBarNotification.getNotification().isColorized();
mShowingPublicInitialized = false;
updateNotificationColor();
if (mMenuRow != null) {
mMenuRow.onNotificationUpdated(mStatusBarNotification);
mMenuRow.setAppName(mAppName);
}
if (mIsSummaryWithChildren) {
mChildrenContainer.recreateNotificationHeader(mExpandClickListener);
mChildrenContainer.onNotificationUpdated();
}
if (mIconAnimationRunning) {
setIconAnimationRunning(true);
}
if (mLastChronometerRunning) {
setChronometerRunning(true);
}
if (mNotificationParent != null) {
mNotificationParent.updateChildrenHeaderAppearance();
}
onChildrenCountChanged();
// The public layouts expand button is always visible
mPublicLayout.updateExpandButtons(true);
updateLimits();
updateIconVisibilities();
updateShelfIconColor();
updateRippleAllowed();
if (mUpdateBackgroundOnUpdate) {
mUpdateBackgroundOnUpdate = false;
updateBackgroundColors();
}
}
/** Called when the notification's ranking was changed (but nothing else changed). */
public void onNotificationRankingUpdated() {
if (mMenuRow != null) {
mMenuRow.onNotificationUpdated(mStatusBarNotification);
}
}
@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,
ContrastColorUtil.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);
}
public void setAboveShelfChangedListener(AboveShelfChangedListener aboveShelfChangedListener) {
mAboveShelfChangedListener = aboveShelfChangedListener;
}
/**
* Sets a supplier that can determine whether the keyguard is secure or not.
* @param secureStateProvider A function that returns true if keyguard is secure.
*/
public void setSecureStateProvider(BooleanSupplier secureStateProvider) {
mSecureStateProvider = secureStateProvider;
}
@Override
public boolean isDimmable() {
if (!getShowingLayout().isDimmable()) {
return false;
}
if (showingAmbientPulsing()) {
return false;
}
return super.isDimmable();
}
private void updateLimits() {
for (NotificationContentView l : mLayouts) {
updateLimitsForView(l);
}
}
private void updateLimitsForView(NotificationContentView layout) {
boolean customView = layout.getContractedChild() != null
&& layout.getContractedChild().getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
int minHeight;
View expandedView = layout.getExpandedChild();
boolean isMediaLayout = expandedView != null
&& expandedView.findViewById(com.android.internal.R.id.media_actions) != null;
boolean showCompactMediaSeekbar = mMediaManager.getShowCompactMediaSeekbar();
if (customView && beforeP && !mIsSummaryWithChildren) {
minHeight = beforeN ? mNotificationMinHeightBeforeN : mNotificationMinHeightBeforeP;
} else if (isMediaLayout && showCompactMediaSeekbar) {
minHeight = mNotificationMinHeightMedia;
} 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 && beforeP) {
headsUpHeight = beforeN ? mMaxHeadsUpHeightBeforeN : mMaxHeadsUpHeightBeforeP;
} else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) {
headsUpHeight = mMaxHeadsUpHeightIncreased;
} else {
headsUpHeight = mMaxHeadsUpHeight;
}
NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper(
VISIBLE_TYPE_HEADSUP);
if (headsUpWrapper != null) {
headsUpHeight = Math.max(headsUpHeight, headsUpWrapper.getMinLayoutHeight());
}
layout.setHeights(minHeight, headsUpHeight, mNotificationMaxHeight, headsUpHeight);
}
public StatusBarNotification getStatusBarNotification() {
return mStatusBarNotification;
}
public NotificationEntry getEntry() {
return mEntry;
}
public boolean isHeadsUp() {
return mIsHeadsUp;
}
public void setHeadsUp(boolean isHeadsUp) {
boolean wasAboveShelf = isAboveShelf();
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) {
mMustStayOnScreen = true;
setAboveShelf(true);
} else if (isAboveShelf() != wasAboveShelf) {
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
}
}
@Override
public boolean showingAmbientPulsing() {
return mIsAmbientPulsing || mAmbientGoingAway;
}
public void setAmbientPulsing(boolean isAmbientPulsing) {
mIsAmbientPulsing = isAmbientPulsing;
}
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);
}
/**
* Set the how much the header should be visible. A value of 0 will make the header fully gone
* and a value of 1 will make the notification look just like normal.
* This is being used for heads up notifications, when they are pinned to the top of the screen
* and the header content is extracted to the statusbar.
*
* @param headerVisibleAmount the amount the header should be visible.
*/
public void setHeaderVisibleAmount(float headerVisibleAmount) {
if (mHeaderVisibleAmount != headerVisibleAmount) {
mHeaderVisibleAmount = headerVisibleAmount;
for (NotificationContentView l : mLayouts) {
l.setHeaderVisibleAmount(headerVisibleAmount);
}
if (mChildrenContainer != null) {
mChildrenContainer.setHeaderVisibleAmount(headerVisibleAmount);
}
notifyHeightChanged(false /* needsAnimation */);
}
}
@Override
public float getHeaderVisibleAmount() {
return mHeaderVisibleAmount;
}
@Override
public void setHeadsUpIsVisible() {
super.setHeadsUpIsVisible();
mMustStayOnScreen = false;
}
/**
* 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);
row.setBottomRoundness(0.0f, false /* animate */);
}
@Override
public boolean isChildInGroup() {
return mNotificationParent != null;
}
/**
* @return whether this notification is the only child in the group summary
*/
public boolean isOnlyChildInGroup() {
return mGroupManager.isOnlyChildInGroup(getStatusBarNotification());
}
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;
if (mExpandAnimationRunning && !isChildInGroup && mNotificationParent != null) {
mNotificationParent.setChildIsExpanding(false);
mNotificationParent.setExtraWidthForClipping(0.0f);
mNotificationParent.setMinimumHeightForClipping(0);
}
mNotificationParent = childInGroup ? parent : null;
mPrivateLayout.setIsChildInGroup(childInGroup);
mNotificationInflater.setIsChildInGroup(childInGroup);
resetBackgroundAlpha();
updateBackgroundForGroupState();
updateClickAndFocus();
if (mNotificationParent != null) {
setOverrideTintColor(NO_COLOR, 0.0f);
// Let's reset the distance to top roundness, as this isn't applied to group children
setDistanceToTopRoundness(NO_ROUNDNESS);
mNotificationParent.updateBackgroundForGroupState();
}
updateIconVisibilities();
updateBackgroundClipping();
}
@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 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 childOrder,
VisualStabilityManager visualStabilityManager,
VisualStabilityManager.Callback callback) {
return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder,
visualStabilityManager, callback);
}
/** Updates states of all children. */
public void updateChildrenStates(AmbientState ambientState) {
if (mIsSummaryWithChildren) {
ExpandableViewState parentState = getViewState();
mChildrenContainer.updateState(parentState, ambientState);
}
}
/** Applies children states. */
public void applyChildrenState() {
if (mIsSummaryWithChildren) {
mChildrenContainer.applyState();
}
}
/** Prepares expansion changed. */
public void prepareExpansionChanged() {
if (mIsSummaryWithChildren) {
mChildrenContainer.prepareExpansionChanged();
}
}
/** Starts child animations. */
public void startChildAnimation(AnimationProperties properties) {
if (mIsSummaryWithChildren) {
mChildrenContainer.startAnimationToState(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();
boolean wasAboveShelf = isAboveShelf();
mIsPinned = pinned;
if (intrinsicHeight != getIntrinsicHeight()) {
notifyHeightChanged(false /* needsAnimation */);
}
if (pinned) {
setIconAnimationRunning(true);
mExpandedWhenPinned = false;
} else if (mExpandedWhenPinned) {
setUserExpanded(true);
}
setChronometerRunning(mLastChronometerRunning);
if (isAboveShelf() != wasAboveShelf) {
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
}
}
@Override
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(), getHeadsUpHeight());
} else if (atLeastMinHeight) {
return Math.max(getCollapsedHeight(), getHeadsUpHeight());
} else {
return getHeadsUpHeight();
}
}
/**
* 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 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 && !shouldShowPublic()) {
return mChildrenContainer.getVisibleHeader();
}
return getShowingLayout().getVisibleNotificationHeader();
}
/**
* @return the contracted notification header. This can be different from
* {@link #getNotificationHeader()} and also {@link #getVisibleNotificationHeader()} and only
* returns the contracted version.
*/
public NotificationHeaderView getContractedNotificationHeader() {
if (mIsSummaryWithChildren) {
return mChildrenContainer.getHeaderView();
}
return mPrivateLayout.getContractedNotificationHeader();
}
public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) {
mOnExpandClickListener = onExpandClickListener;
}
public void setLongPressListener(LongPressListener longPressListener) {
mLongPressListener = longPressListener;
}
@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 HeadsUpManager getHeadsUpManager() {
return mHeadsUpManager;
}
public void setGutsView(MenuItem item) {
if (mGuts != null && item.getGutsView() instanceof NotificationGuts.GutsContent) {
((NotificationGuts.GutsContent) item.getGutsView()).setGutsParent(mGuts);
mGuts.setGutsContent((NotificationGuts.GutsContent) item.getGutsView());
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mEntry.setInitializationTime(SystemClock.elapsedRealtime());
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 != null && mMenuRow.getMenuView() != null;
if (existed) {
removeView(mMenuRow.getMenuView());
}
if (plugin == null) {
return;
}
mMenuRow = plugin;
if (mMenuRow.shouldUseDefaultMenuItems()) {
ArrayList