Plugin介绍

Plugin是一个在SystemUI中的接口,通过其代码注释可以了解其用途(frameworks/base/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/Plugin.java):

/*** Plugins are separate APKs that* are expected to implement interfaces provided by SystemUI.  Their* code is dynamically loaded into the SysUI process which can allow* for multiple prototypes to be created and run on a single android* build.** PluginLifecycle:* <pre class="prettyprint">** plugin.onCreate(Context sysuiContext, Context pluginContext);* --- This is always called before any other calls** pluginListener.onPluginConnected(Plugin p);* --- This lets the plugin hook know that a plugin is now connected.** ** Any other calls back and forth between sysui/plugin **** pluginListener.onPluginDisconnected(Plugin p);* --- Lets the plugin hook know that it should stop interacting with*     this plugin and drop all references to it.** plugin.onDestroy();* --- Finally the plugin can perform any cleanup to ensure that its not*     leaking into the SysUI process.** Any time a plugin APK is updated the plugin is destroyed and recreated* to load the new code/resources.** </pre>** Creating plugin hooks:** To create a plugin hook, first create an interface in* frameworks/base/packages/SystemUI/plugin that extends Plugin.* Include in it any hooks you want to be able to call into from* sysui and create callback interfaces for anything you need to* pass through into the plugin.** Then to attach to any plugins simply add a plugin listener and* onPluginConnected will get called whenever new plugins are installed,* updated, or enabled.  Like this example from SystemUIApplication:** <pre class="prettyprint">* {@literal* PluginManager.getInstance(this).addPluginListener(OverlayPlugin.COMPONENT,*        new PluginListener<OverlayPlugin>() {*        @Override*        public void onPluginConnected(OverlayPlugin plugin) {*            StatusBar phoneStatusBar = getComponent(StatusBar.class);*            if (phoneStatusBar != null) {*                plugin.setup(phoneStatusBar.getStatusBarWindow(),*                phoneStatusBar.getNavigationBarView());*            }*        }* }, OverlayPlugin.VERSION, true /* Allow multiple plugins *\/);* }* </pre>* Note the VERSION included here.  Any time incompatible changes in the* interface are made, this version should be changed to ensure old plugins* aren't accidentally loaded.  Since the plugin library is provided by* SystemUI, default implementations can be added for new methods to avoid* version changes when possible.** Implementing a Plugin:** See the ExamplePlugin for an example Android.mk on how to compile* a plugin.  Note that SystemUILib is not static for plugins, its classes* are provided by SystemUI.** Plugin security is based around a signature permission, so plugins must* hold the following permission in their manifest.** <pre class="prettyprint">* {@literal* <uses-permission android:name="com.android.systemui.permission.PLUGIN" />* }* </pre>** A plugin is found through a querying for services, so to let SysUI know* about it, create a service with a name that points at your implementation* of the plugin interface with the action accompanying it:** <pre class="prettyprint">* {@literal* <service android:name=".TestOverlayPlugin">*    <intent-filter>*        <action android:name="com.android.systemui.action.PLUGIN_COMPONENT" />*    </intent-filter>* </service>* }* </pre>*/
public interface Plugin {/*** @deprecated* @see Requires*/default int getVersion() {// Default of -1 indicates the plugin supports the new Requires model.return -1;}default void onCreate(Context sysuiContext, Context pluginContext) {}default void onDestroy() {}
}

使用示例

NotificationMenuRowPlugin是一个继承Plugin的接口(frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java):

@ProvidesInterface(action = NotificationMenuRowPlugin.ACTION,version = NotificationMenuRowPlugin.VERSION)
@DependsOn(target = OnMenuEventListener.class)
@DependsOn(target = MenuItem.class)
@DependsOn(target = NotificationSwipeActionHelper.class)
@DependsOn(target = SnoozeOption.class)
public interface NotificationMenuRowPlugin extends Plugin {public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW";public static final int VERSION = 5;@ProvidesInterface(version = OnMenuEventListener.VERSION)public interface OnMenuEventListener {public static final int VERSION = 1;public void onMenuClicked(View row, int x, int y, MenuItem menu);public void onMenuReset(View row);public void onMenuShown(View row);}@ProvidesInterface(version = MenuItem.VERSION)public interface MenuItem {public static final int VERSION = 1;public View getMenuView();public View getGutsView();public String getContentDescription();}/*** @return a list of items to populate the menu 'behind' a notification.*/public ArrayList<MenuItem> getMenuItems(Context context);/*** @return the {@link MenuItem} to display when a notification is long pressed.*/public MenuItem getLongpressMenuItem(Context context);/*** @return the {@link MenuItem} to display when app ops icons are pressed.*/public MenuItem getAppOpsMenuItem(Context context);/*** @return the {@link MenuItem} to display when snooze item is pressed.*/public MenuItem getSnoozeMenuItem(Context context);public void setMenuItems(ArrayList<MenuItem> items);/*** If this returns {@code true}, then the menu row will bind and fade in the notification guts* view for the menu item it holds.** @see #menuItemToExposeOnSnap()* @return whether or not to immediately expose the notification guts*/default boolean shouldShowGutsOnSnapOpen() {return false;}/*** When #shouldShowGutsOnExpose is true, this method must return the menu item to expose on* #onSnapOpen. Otherwise we will fall back to the default behavior of fading in the menu row** @return the {@link MenuItem} containing the NotificationGuts that should be exposed*/@Nullabledefault MenuItem menuItemToExposeOnSnap() {return null;}/*** Get the origin for the circular reveal animation when expanding the notification guts. Only* used when #shouldShowGutsOnSnapOpen is true* @return the x,y coordinates for the start of the animation*/@Nullabledefault Point getRevealAnimationOrigin() {return new Point(0, 0);}public void setMenuClickListener(OnMenuEventListener listener);public void setAppName(String appName);public void createMenu(ViewGroup parent, StatusBarNotification sbn);public void resetMenu();public View getMenuView();/*** Get the target position that a notification row should be snapped open to in order to reveal* the menu. This is generally determined by the number of icons in the notification menu and the* size of each icon. This method accounts for whether the menu appears on the left or ride side* of the parent notification row.** @return an int representing the x-offset in pixels that the notification should snap open to.* Positive values imply that the notification should be offset to the right to reveal the menu,* and negative alues imply that the notification should be offset to the right.*/public int getMenuSnapTarget();/*** Determines whether or not the menu should be shown in response to user input.* @return true if the menu should be shown, false otherwise.*/public boolean shouldShowMenu();/*** Determines whether the menu is currently visible.* @return true if the menu is visible, false otherwise.*/public boolean isMenuVisible();/*** Determines whether a given movement is towards or away from the current location of the menu.* @param movement* @return true if the movement is towards the menu, false otherwise.*/public boolean isTowardsMenu(float movement);/*** Determines whether the menu should snap closed instead of dismissing the* parent notification, as a function of its current state.** @return true if the menu should snap closed, false otherwise.*/public boolean shouldSnapBack();/*** Determines whether the menu was previously snapped open to the same side that it is currently* being shown on.* @return true if the menu is snapped open to the same side on which it currently appears,* false otherwise.*/public boolean isSnappedAndOnSameSide();/*** Determines whether the notification the menu is attached to is able to be dismissed.* @return true if the menu's parent notification is dismissable, false otherwise.*/public boolean canBeDismissed();/*** Informs the menu whether dismiss gestures are left-to-right or right-to-left.*/default void setDismissRtl(boolean dismissRtl) {}/*** Determines whether the menu should remain open given its current state, or snap closed.* @return true if the menu should remain open, false otherwise.*/public boolean isWithinSnapMenuThreshold();/*** Determines whether the menu has been swiped far enough to snap open.* @return true if the menu has been swiped far enough to open, false otherwise.*/public boolean isSwipedEnoughToShowMenu();public default boolean onInterceptTouchEvent(View view, MotionEvent ev) {return false;}public default boolean shouldUseDefaultMenuItems() {return false;}/*** Callback used to signal the menu that its parent's translation has changed.* @param translation The new x-translation of the menu as a position (not an offset).*/public void onParentTranslationUpdate(float translation);/*** Callback used to signal the menu that its parent's height has changed.*/public void onParentHeightUpdate();/*** Callback used to signal the menu that its parent notification has been updated.* @param sbn*/public void onNotificationUpdated(StatusBarNotification sbn);/*** Callback used to signal the menu that a user is moving the parent notification.* @param delta The change in the parent notification's position.*/public void onTouchMove(float delta);/*** Callback used to signal the menu that a user has begun touching its parent notification.*/public void onTouchStart();/*** Callback used to signal the menu that a user has finished touching its parent notification.*/public void onTouchEnd();/*** Callback used to signal the menu that it has been snapped closed.*/public void onSnapClosed();/*** Callback used to signal the menu that it has been snapped open.*/public void onSnapOpen();/*** Callback used to signal the menu that its parent notification has been dismissed.*/public void onDismiss();public default void onConfigurationChanged() { }}

NotificationMenuRow实现了NotificationMenuRowPlugin接口(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java):

public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnClickListener,ExpandableNotificationRow.LayoutListener {private static final boolean DEBUG = false;private static final String TAG = "swipe";// Notification must be swiped at least this fraction of a single menu item to show menuprivate static final float SWIPED_FAR_ENOUGH_MENU_FRACTION = 0.25f;private static final float SWIPED_FAR_ENOUGH_MENU_UNCLEARABLE_FRACTION = 0.15f;// When the menu is displayed, the notification must be swiped within this fraction of a single// menu item to snap back to menu (else it will cover the menu or it'll be dismissed)private static final float SWIPED_BACK_ENOUGH_TO_COVER_FRACTION = 0.2f;private static final int ICON_ALPHA_ANIM_DURATION = 200;private static final long SHOW_MENU_DELAY = 60;private ExpandableNotificationRow mParent;private Context mContext;private FrameLayout mMenuContainer;private NotificationMenuItem mInfoItem;private MenuItem mAppOpsItem;private MenuItem mSnoozeItem;private ArrayList<MenuItem> mLeftMenuItems;private ArrayList<MenuItem> mRightMenuItems;private final Map<View, MenuItem> mMenuItemsByView = new ArrayMap<>();private OnMenuEventListener mMenuListener;private boolean mDismissRtl;private boolean mIsForeground;private ValueAnimator mFadeAnimator;private boolean mAnimating;private boolean mMenuFadedIn;private boolean mOnLeft;private boolean mIconsPlaced;private boolean mDismissing;private boolean mSnapping;private float mTranslation;private int[] mIconLocation = new int[2];private int[] mParentLocation = new int[2];private int mHorizSpaceForIcon = -1;private int mVertSpaceForIcons = -1;private int mIconPadding = -1;private int mSidePadding;private float mAlpha = 0f;private CheckForDrag mCheckForDrag;private Handler mHandler;private boolean mMenuSnapped;private boolean mMenuSnappedOnLeft;private boolean mShouldShowMenu;private boolean mIsUserTouching;private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;public NotificationMenuRow(Context context,PeopleNotificationIdentifier peopleNotificationIdentifier) {mContext = context;mShouldShowMenu = context.getResources().getBoolean(R.bool.config_showNotificationGear);mHandler = new Handler(Looper.getMainLooper());mLeftMenuItems = new ArrayList<>();mRightMenuItems = new ArrayList<>();mPeopleNotificationIdentifier = peopleNotificationIdentifier;}@Overridepublic ArrayList<MenuItem> getMenuItems(Context context) {return mOnLeft ? mLeftMenuItems : mRightMenuItems;}@Overridepublic MenuItem getLongpressMenuItem(Context context) {return mInfoItem;}@Overridepublic MenuItem getAppOpsMenuItem(Context context) {return mAppOpsItem;}@Overridepublic MenuItem getSnoozeMenuItem(Context context) {return mSnoozeItem;}@VisibleForTestingprotected ExpandableNotificationRow getParent() {return mParent;}@VisibleForTestingprotected boolean isMenuOnLeft() {return mOnLeft;}@VisibleForTestingprotected boolean isMenuSnappedOnLeft() {return mMenuSnappedOnLeft;}@VisibleForTestingprotected boolean isMenuSnapped() {return mMenuSnapped;}@VisibleForTestingprotected boolean isDismissing() {return mDismissing;}@VisibleForTestingprotected boolean isSnapping() {return mSnapping;}@Overridepublic void setMenuClickListener(OnMenuEventListener listener) {mMenuListener = listener;}@Overridepublic void createMenu(ViewGroup parent, StatusBarNotification sbn) {mParent = (ExpandableNotificationRow) parent;createMenuViews(true /* resetState */,sbn != null && (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)!= 0);}@Overridepublic boolean isMenuVisible() {return mAlpha > 0;}@VisibleForTestingprotected boolean isUserTouching() {return mIsUserTouching;}@Overridepublic boolean shouldShowMenu() {return mShouldShowMenu;}@Overridepublic View getMenuView() {return mMenuContainer;}@VisibleForTestingprotected float getTranslation() {return mTranslation;}@Overridepublic void resetMenu() {resetState(true);}@Overridepublic void onTouchEnd() {mIsUserTouching = false;}@Overridepublic void onNotificationUpdated(StatusBarNotification sbn) {if (mMenuContainer == null) {// Menu hasn't been created yet, no need to do anything.return;}createMenuViews(!isMenuVisible() /* resetState */,(sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0);}@Overridepublic void onConfigurationChanged() {mParent.setLayoutListener(this);}@Overridepublic void onLayout() {mIconsPlaced = false; // Force icons to be re-placedsetMenuLocation();mParent.removeListener();}private void createMenuViews(boolean resetState, final boolean isForeground) {mIsForeground = isForeground;final Resources res = mContext.getResources();mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);mLeftMenuItems.clear();mRightMenuItems.clear();boolean showSnooze = Settings.Secure.getInt(mContext.getContentResolver(),SHOW_NOTIFICATION_SNOOZE, 0) == 1;// Construct the menu items based on the notificationif (!isForeground && showSnooze) {// Only show snooze for non-foreground notifications, and if the setting is onmSnoozeItem = createSnoozeItem(mContext);}mAppOpsItem = createAppOpsItem(mContext);NotificationEntry entry = mParent.getEntry();int personNotifType = mPeopleNotificationIdentifier.getPeopleNotificationType(entry.getSbn(), entry.getRanking());if (personNotifType == PeopleNotificationIdentifier.TYPE_PERSON) {mInfoItem = createPartialConversationItem(mContext);} else if (personNotifType >= PeopleNotificationIdentifier.TYPE_FULL_PERSON) {mInfoItem = createConversationItem(mContext);} else {mInfoItem = createInfoItem(mContext);}if (!isForeground && showSnooze) {mRightMenuItems.add(mSnoozeItem);}mRightMenuItems.add(mInfoItem);mRightMenuItems.add(mAppOpsItem);mLeftMenuItems.addAll(mRightMenuItems);populateMenuViews();if (resetState) {resetState(false /* notify */);} else {mIconsPlaced = false;setMenuLocation();if (!mIsUserTouching) {onSnapOpen();}}}private void populateMenuViews() {if (mMenuContainer != null) {mMenuContainer.removeAllViews();mMenuItemsByView.clear();} else {mMenuContainer = new FrameLayout(mContext);}List<MenuItem> menuItems = mOnLeft ? mLeftMenuItems : mRightMenuItems;for (int i = 0; i < menuItems.size(); i++) {addMenuView(menuItems.get(i), mMenuContainer);}}private void resetState(boolean notify) {setMenuAlpha(0f);mIconsPlaced = false;mMenuFadedIn = false;mAnimating = false;mSnapping = false;mDismissing = false;mMenuSnapped = false;setMenuLocation();if (mMenuListener != null && notify) {mMenuListener.onMenuReset(mParent);}}@Overridepublic void onTouchMove(float delta) {mSnapping = false;if (!isTowardsMenu(delta) && isMenuLocationChange()) {// Don't consider it "snapped" if location has changed.mMenuSnapped = false;// Changed directions, make sure we check to fade in icon again.if (!mHandler.hasCallbacks(mCheckForDrag)) {// No check scheduled, set null to schedule a new one.mCheckForDrag = null;} else {// Check scheduled, reset alpha and update location; check will fade it insetMenuAlpha(0f);setMenuLocation();}}if (mShouldShowMenu&& !NotificationStackScrollLayout.isPinnedHeadsUp(getParent())&& !mParent.areGutsExposed()&& !mParent.showingPulsing()&& (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag))) {// Only show the menu if we're not a heads up view and guts aren't exposed.mCheckForDrag = new CheckForDrag();mHandler.postDelayed(mCheckForDrag, SHOW_MENU_DELAY);}}@VisibleForTestingprotected void beginDrag() {mSnapping = false;if (mFadeAnimator != null) {mFadeAnimator.cancel();}mHandler.removeCallbacks(mCheckForDrag);mCheckForDrag = null;mIsUserTouching = true;}@Overridepublic void onTouchStart() {beginDrag();}@Overridepublic void onSnapOpen() {mMenuSnapped = true;mMenuSnappedOnLeft = isMenuOnLeft();if (mAlpha == 0f && mParent != null) {fadeInMenu(mParent.getWidth());}if (mMenuListener != null) {mMenuListener.onMenuShown(getParent());}}@Overridepublic void onSnapClosed() {cancelDrag();mMenuSnapped = false;mSnapping = true;}@Overridepublic void onDismiss() {cancelDrag();mMenuSnapped = false;mDismissing = true;}@VisibleForTestingprotected void cancelDrag() {if (mFadeAnimator != null) {mFadeAnimator.cancel();}mHandler.removeCallbacks(mCheckForDrag);}@VisibleForTestingprotected float getMinimumSwipeDistance() {final float multiplier = getParent().canViewBeDismissed()? SWIPED_FAR_ENOUGH_MENU_FRACTION: SWIPED_FAR_ENOUGH_MENU_UNCLEARABLE_FRACTION;return mHorizSpaceForIcon * multiplier;}@VisibleForTestingprotected float getMaximumSwipeDistance() {return mHorizSpaceForIcon * SWIPED_BACK_ENOUGH_TO_COVER_FRACTION;}/*** Returns whether the gesture is towards the menu location or not.*/@Overridepublic boolean isTowardsMenu(float movement) {return isMenuVisible()&& ((isMenuOnLeft() && movement <= 0)|| (!isMenuOnLeft() && movement >= 0));}@Overridepublic void setAppName(String appName) {if (appName == null) {return;}setAppName(appName, mLeftMenuItems);setAppName(appName, mRightMenuItems);}private void setAppName(String appName,ArrayList<MenuItem> menuItems) {Resources res = mContext.getResources();final int count = menuItems.size();for (int i = 0; i < count; i++) {MenuItem item = menuItems.get(i);String description = String.format(res.getString(R.string.notification_menu_accessibility),appName, item.getContentDescription());View menuView = item.getMenuView();if (menuView != null) {menuView.setContentDescription(description);}}}@Overridepublic void onParentHeightUpdate() {if (mParent == null|| (mLeftMenuItems.isEmpty() && mRightMenuItems.isEmpty())|| mMenuContainer == null) {return;}int parentHeight = mParent.getActualHeight();float translationY;if (parentHeight < mVertSpaceForIcons) {translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2);} else {translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2;}mMenuContainer.setTranslationY(translationY);}@Overridepublic void onParentTranslationUpdate(float translation) {mTranslation = translation;if (mAnimating || !mMenuFadedIn) {// Don't adjust when animating, or if the menu hasn't been shown yet.return;}final float fadeThreshold = mParent.getWidth() * 0.3f;final float absTrans = Math.abs(translation);float desiredAlpha = 0;if (absTrans == 0) {desiredAlpha = 0;} else if (absTrans <= fadeThreshold) {desiredAlpha = 1;} else {desiredAlpha = 1 - ((absTrans - fadeThreshold) / (mParent.getWidth() - fadeThreshold));}setMenuAlpha(desiredAlpha);}@Overridepublic void onClick(View v) {if (mMenuListener == null) {// Nothing to doreturn;}v.getLocationOnScreen(mIconLocation);mParent.getLocationOnScreen(mParentLocation);final int centerX = mHorizSpaceForIcon / 2;final int centerY = v.getHeight() / 2;final int x = mIconLocation[0] - mParentLocation[0] + centerX;final int y = mIconLocation[1] - mParentLocation[1] + centerY;if (mMenuItemsByView.containsKey(v)) {mMenuListener.onMenuClicked(mParent, x, y, mMenuItemsByView.get(v));}}private boolean isMenuLocationChange() {boolean onLeft = mTranslation > mIconPadding;boolean onRight = mTranslation < -mIconPadding;if ((isMenuOnLeft() && onRight) || (!isMenuOnLeft() && onLeft)) {return true;}return false;}private void setMenuLocation() {boolean showOnLeft = mTranslation > 0;if ((mIconsPlaced && showOnLeft == isMenuOnLeft()) || isSnapping() || mMenuContainer == null|| !mMenuContainer.isAttachedToWindow()) {// Do nothingreturn;}boolean wasOnLeft = mOnLeft;mOnLeft = showOnLeft;if (wasOnLeft != showOnLeft) {populateMenuViews();}final int count = mMenuContainer.getChildCount();for (int i = 0; i < count; i++) {final View v = mMenuContainer.getChildAt(i);final float left = i * mHorizSpaceForIcon;final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1));v.setX(showOnLeft ? left : right);}mIconsPlaced = true;}@VisibleForTestingprotected void setMenuAlpha(float alpha) {mAlpha = alpha;if (mMenuContainer == null) {return;}if (alpha == 0) {mMenuFadedIn = false; // Can fade in again once it's gone.mMenuContainer.setVisibility(View.INVISIBLE);} else {mMenuContainer.setVisibility(View.VISIBLE);}final int count = mMenuContainer.getChildCount();for (int i = 0; i < count; i++) {mMenuContainer.getChildAt(i).setAlpha(mAlpha);}}/*** Returns the horizontal space in pixels required to display the menu.*/@VisibleForTestingprotected int getSpaceForMenu() {return mHorizSpaceForIcon * mMenuContainer.getChildCount();}private final class CheckForDrag implements Runnable {@Overridepublic void run() {final float absTransX = Math.abs(mTranslation);final float bounceBackToMenuWidth = getSpaceForMenu();final float notiThreshold = mParent.getWidth() * 0.4f;if ((!isMenuVisible() || isMenuLocationChange())&& absTransX >= bounceBackToMenuWidth * 0.4&& absTransX < notiThreshold) {fadeInMenu(notiThreshold);}}}private void fadeInMenu(final float notiThreshold) {if (mDismissing || mAnimating) {return;}if (isMenuLocationChange()) {setMenuAlpha(0f);}final float transX = mTranslation;final boolean fromLeft = mTranslation > 0;setMenuLocation();mFadeAnimator = ValueAnimator.ofFloat(mAlpha, 1);mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {final float absTrans = Math.abs(transX);boolean pastMenu = (fromLeft && transX <= notiThreshold)|| (!fromLeft && absTrans <= notiThreshold);if (pastMenu && !mMenuFadedIn) {setMenuAlpha((float) animation.getAnimatedValue());}}});mFadeAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationStart(Animator animation) {mAnimating = true;}@Overridepublic void onAnimationCancel(Animator animation) {// TODO should animate back to 0f from current alphasetMenuAlpha(0f);}@Overridepublic void onAnimationEnd(Animator animation) {mAnimating = false;mMenuFadedIn = mAlpha == 1;}});mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);mFadeAnimator.setDuration(ICON_ALPHA_ANIM_DURATION);mFadeAnimator.start();}@Overridepublic void setMenuItems(ArrayList<MenuItem> items) {// Do nothing we use our own for now.// TODO -- handle / allow custom menu items!}@Overridepublic boolean shouldShowGutsOnSnapOpen() {return false;}@Overridepublic MenuItem menuItemToExposeOnSnap() {return null;}@Overridepublic Point getRevealAnimationOrigin() {View v = mInfoItem.getMenuView();int menuX = v.getLeft() + v.getPaddingLeft() + (v.getWidth() / 2);int menuY = v.getTop() + v.getPaddingTop() + (v.getHeight() / 2);if (isMenuOnLeft()) {return new Point(menuX, menuY);} else {menuX = mParent.getRight() - menuX;return new Point(menuX, menuY);}}static MenuItem createSnoozeItem(Context context) {Resources res = context.getResources();NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(context).inflate(R.layout.notification_snooze, null, false);String snoozeDescription = res.getString(R.string.notification_menu_snooze_description);MenuItem snooze = new NotificationMenuItem(context, snoozeDescription, content,R.drawable.ic_snooze);return snooze;}static NotificationMenuItem createConversationItem(Context context) {Resources res = context.getResources();String infoDescription = res.getString(R.string.notification_menu_gear_description);NotificationConversationInfo infoContent =(NotificationConversationInfo) LayoutInflater.from(context).inflate(R.layout.notification_conversation_info, null, false);return new NotificationMenuItem(context, infoDescription, infoContent,R.drawable.ic_settings);}static NotificationMenuItem createPartialConversationItem(Context context) {Resources res = context.getResources();String infoDescription = res.getString(R.string.notification_menu_gear_description);PartialConversationInfo infoContent =(PartialConversationInfo) LayoutInflater.from(context).inflate(R.layout.partial_conversation_info, null, false);return new NotificationMenuItem(context, infoDescription, infoContent,R.drawable.ic_settings);}static NotificationMenuItem createInfoItem(Context context) {Resources res = context.getResources();String infoDescription = res.getString(R.string.notification_menu_gear_description);NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate(R.layout.notification_info, null, false);return new NotificationMenuItem(context, infoDescription, infoContent,R.drawable.ic_settings);}static MenuItem createAppOpsItem(Context context) {AppOpsInfo appOpsContent = (AppOpsInfo) LayoutInflater.from(context).inflate(R.layout.app_ops_info, null, false);MenuItem info = new NotificationMenuItem(context, null, appOpsContent,-1 /*don't show in slow swipe menu */);return info;}private void addMenuView(MenuItem item, ViewGroup parent) {View menuView = item.getMenuView();if (menuView != null) {menuView.setAlpha(mAlpha);parent.addView(menuView);menuView.setOnClickListener(this);FrameLayout.LayoutParams lp = (LayoutParams) menuView.getLayoutParams();lp.width = mHorizSpaceForIcon;lp.height = mHorizSpaceForIcon;menuView.setLayoutParams(lp);}mMenuItemsByView.put(menuView, item);}@VisibleForTesting/*** Determine the minimum offset below which the menu should snap back closed.*/protected float getSnapBackThreshold() {return getSpaceForMenu() - getMaximumSwipeDistance();}/*** Determine the maximum offset above which the parent notification should be dismissed.* @return*/@VisibleForTestingprotected float getDismissThreshold() {return getParent().getWidth() * SWIPED_FAR_ENOUGH_SIZE_FRACTION;}@Overridepublic boolean isWithinSnapMenuThreshold() {float translation = getTranslation();float snapBackThreshold = getSnapBackThreshold();float targetRight = getDismissThreshold();return isMenuOnLeft()? translation > snapBackThreshold && translation < targetRight: translation < -snapBackThreshold && translation > -targetRight;}@Overridepublic boolean isSwipedEnoughToShowMenu() {final float minimumSwipeDistance = getMinimumSwipeDistance();final float translation = getTranslation();return isMenuVisible() && (isMenuOnLeft() ?translation > minimumSwipeDistance: translation < -minimumSwipeDistance);}@Overridepublic int getMenuSnapTarget() {return isMenuOnLeft() ? getSpaceForMenu() : -getSpaceForMenu();}@Overridepublic boolean shouldSnapBack() {float translation = getTranslation();float targetLeft = getSnapBackThreshold();return isMenuOnLeft() ? translation < targetLeft : translation > -targetLeft;}@Overridepublic boolean isSnappedAndOnSameSide() {return isMenuSnapped() && isMenuVisible()&& isMenuSnappedOnLeft() == isMenuOnLeft();}@Overridepublic boolean canBeDismissed() {return getParent().canViewBeDismissed();}@Overridepublic void setDismissRtl(boolean dismissRtl) {mDismissRtl = dismissRtl;if (mMenuContainer != null) {createMenuViews(true, mIsForeground);}}public static class NotificationMenuItem implements MenuItem {View mMenuView;GutsContent mGutsContent;String mContentDescription;/*** Add a new 'guts' panel. If iconResId < 0 it will not appear in the slow swipe menu* but can still be exposed via other affordances.*/public NotificationMenuItem(Context context, String contentDescription, GutsContent content,int iconResId) {Resources res = context.getResources();int padding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);int tint = res.getColor(R.color.notification_gear_color);if (iconResId >= 0) {AlphaOptimizedImageView iv = new AlphaOptimizedImageView(context);iv.setPadding(padding, padding, padding, padding);Drawable icon = context.getResources().getDrawable(iconResId);iv.setImageDrawable(icon);iv.setColorFilter(tint);iv.setAlpha(1f);mMenuView = iv;}mContentDescription = contentDescription;mGutsContent = content;}@Override@Nullablepublic View getMenuView() {return mMenuView;}@Overridepublic View getGutsView() {return mGutsContent.getContentView();}@Overridepublic String getContentDescription() {return mContentDescription;}}
}

ExpandableNotificationRow实现了PluginListener接口,同时也是NotificationMenuRowPlugin的使用者(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java):

/*** 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 ActivatableNotificationViewimplements PluginListener<NotificationMenuRowPlugin>, SwipeableView,NotificationListItem {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();}/** Listens for changes to the expansion state of this row. */public interface OnExpansionChangedListener {void onExpansionChanged(boolean isExpanded);}private StatusBarStateController mStatusbarStateController;private KeyguardBypassController mBypassController;private LayoutListener mLayoutListener;private RowContentBindStage mRowContentBindStage;private PeopleNotificationIdentifier mPeopleNotificationIdentifier;private int mIconTransformContentShift;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;private Animator mTranslateAnim;private ArrayList<View> 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 String mAppName;private FalsingManager mFalsingManager;/*** 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 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 boolean mExpandAnimationRunning;private AboveShelfChangedListener mAboveShelfChangedListener;private HeadsUpManager mHeadsUpManager;private Consumer<Boolean> 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() {@Overridepublic void onClick(View v) {if (!shouldShowPublic() && (!mIsLowPriority || isExpanded())&& mGroupManager.isSummaryOfGroup(mEntry.getSbn())) {mGroupExpansionChanging = true;final boolean wasExpanded = mGroupManager.isGroupExpanded(mEntry.getSbn());boolean nowExpanded = mGroupManager.toggleGroupExpansion(mEntry.getSbn());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;// Also notify any expansion changed listeners. This is necessary since the// expansion doesn't actually change (it's already system expanded) but it// changes visuallyif (mExpansionChangedListener != null) {mExpansionChangedListener.onExpansionChanged(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 mKeepInParent;private boolean mRemoved;private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT =new FloatProperty<ExpandableNotificationRow>("translate") {@Overridepublic void setValue(ExpandableNotificationRow object, float value) {object.setTranslation(value);}@Overridepublic Float get(ExpandableNotificationRow object) {return object.getTranslation();}};private OnClickListener mOnClickListener;private boolean mHeadsupDisappearRunning;private View mChildAfterViewWhenDismissed;private View mGroupParentWhenDismissed;private boolean mShelfIconVisible;private boolean mAboveShelf;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;@Nullable private OnExpansionChangedListener mExpansionChangedListener;@Nullable private Runnable mOnIntrinsicHeightReachedRunnable;private SystemNotificationAsyncTask mSystemNotificationAsyncTask =new SystemNotificationAsyncTask();/*** Returns whether the given {@code statusBarNotification} is a system notification.* <b>Note</b>, 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;}public NotificationContentView[] getLayouts() {return Arrays.copyOf(mLayouts, mLayouts.length);}/*** Is this entry pinned and was expanded while doing so*/public boolean isPinnedAndExpanded() {if (!isPinned()) {return false;}return mExpandedWhenPinned;}@Overridepublic boolean isGroupExpansionChanging() {if (isChildInGroup()) {return mNotificationParent.isGroupExpansionChanging();}return mGroupExpansionChanging;}public void setGroupExpansionChanging(boolean changing) {mGroupExpansionChanging = changing;}@Overridepublic 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.getAttachedChildren();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;cacheIsSystemNotification();}/*** 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* @deprecated By default, {@link NotificationContentInflater#unbindContent} will tell the* view hierarchy to only free when the view is safe to remove so this method is no longer* needed. Will remove when all uses are gone.*/@Deprecatedpublic void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);params.markContentViewsFreeable(inflationFlag);mRowContentBindStage.requestRebind(mEntry, null /* callback */);}/*** 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() {//TODO: This probably shouldn't be in ExpandableNotificationRowif (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(mEntry.getSbn().getPackageName(),mEntry.getChannel().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, mEntry.getSbn());}isNonblockable |= mEntry.getChannel().isImportanceLockedByOEM();isNonblockable |= mEntry.getChannel().isImportanceLockedByCriticalDeviceFunction();if (!isNonblockable && mEntry != null && mEntry.mIsSystemNotification != null) {if (mEntry.mIsSystemNotification) {if (mEntry.getChannel() != null&& !mEntry.getChannel().isBlockable()) {isNonblockable = true;}}}return isNonblockable;}private boolean isConversation() {return mPeopleNotificationIdentifier.getPeopleNotificationType(mEntry.getSbn(), mEntry.getRanking())!= PeopleNotificationIdentifier.TYPE_NON_PERSON;}public void onNotificationUpdated() {for (NotificationContentView l : mLayouts) {l.onNotificationUpdated(mEntry);}mIsColorized = mEntry.getSbn().getNotification().isColorized();mShowingPublicInitialized = false;updateNotificationColor();if (mMenuRow != null) {mMenuRow.onNotificationUpdated(mEntry.getSbn());mMenuRow.setAppName(mAppName);}if (mIsSummaryWithChildren) {mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation());mChildrenContainer.onNotificationUpdated();}if (mIconAnimationRunning) {setIconAnimationRunning(true);}if (mLastChronometerRunning) {setChronometerRunning(true);}if (mNotificationParent != null) {mNotificationParent.updateChildrenHeaderAppearance();}onAttachedChildrenCountChanged();// The public layouts expand button is always visiblemPublicLayout.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(mEntry.getSbn());}}/** Call when bubble state has changed and the button on the notification should be updated. */public void updateBubbleButton() {for (NotificationContentView l : mLayouts) {l.updateBubbleButton(mEntry);}}@VisibleForTestingvoid updateShelfIconColor() {StatusBarIconView expandedIcon = mEntry.getIcons().getShelfIcon();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) {color = getOriginalIconColor();}expandedIcon.setStaticDrawableColor(color);}public int getOriginalIconColor() {if (mIsSummaryWithChildren && !shouldShowPublic()) {return mChildrenContainer.getVisibleHeader().getOriginalIconColor();}int color = getShowingLayout().getOriginalIconColor();if (color != Notification.COLOR_INVALID) {return color;} else {return mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded(),getBackgroundColorWithoutTint());}}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;}@Overridepublic boolean isDimmable() {if (!getShowingLayout().isDimmable()) {return false;}if (showingPulsing()) {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);}@NonNullpublic NotificationEntry getEntry() {return mEntry;}@Overridepublic 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);}}@Overridepublic boolean showingPulsing() {return isHeadsUpState() && (isDozing() || (mOnKeyguard && isBypassEnabled()));}/*** @return if the view is in heads up state, i.e either still heads upped or it's disappearing.*/public boolean isHeadsUpState() {return mIsHeadsUp || mHeadsupDisappearRunning;}public void setRemoteInputController(RemoteInputController r) {mPrivateLayout.setRemoteInputController(r);}String getAppName() {return 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 */);}}@Overridepublic float getHeaderVisibleAmount() {return mHeaderVisibleAmount;}@Overridepublic void setHeadsUpIsVisible() {super.setHeadsUpIsVisible();mMustStayOnScreen = false;}/*** @see NotificationChildrenContainer#setUntruncatedChildCount(int)*/public void setUntruncatedChildCount(int childCount) {if (mChildrenContainer == null) {mChildrenContainerStub.inflate();}mChildrenContainer.setUntruncatedChildCount(childCount);}/*** 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);onAttachedChildrenCountChanged();row.setIsChildInGroup(true, this);}/*** Same as {@link #addChildNotification(ExpandableNotificationRow, int)}, but takes a* {@link NotificationListItem} instead** @param childItem item* @param childIndex index*/public void addChildNotification(NotificationListItem childItem, int childIndex) {addChildNotification((ExpandableNotificationRow) childItem.getView(), childIndex);}public void removeChildNotification(ExpandableNotificationRow row) {if (mChildrenContainer != null) {mChildrenContainer.removeNotification(row);}onAttachedChildrenCountChanged();row.setIsChildInGroup(false, null);row.setBottomRoundness(0.0f, false /* animate */);}@Overridepublic void removeChildNotification(NotificationListItem child) {removeChildNotification((ExpandableNotificationRow) child.getView());}@Overridepublic boolean isChildInGroup() {return mNotificationParent != null;}/*** @return whether this notification is the only child in the group summary*/public boolean isOnlyChildInGroup() {return mGroupManager.isOnlyChildInGroup(mEntry.getSbn());}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) {if (mExpandAnimationRunning && !isChildInGroup && mNotificationParent != null) {mNotificationParent.setChildIsExpanding(false);mNotificationParent.setExtraWidthForClipping(0.0f);mNotificationParent.setMinimumHeightForClipping(0);}mNotificationParent = isChildInGroup ? parent : null;mPrivateLayout.setIsChildInGroup(isChildInGroup);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 childrensetDistanceToTopRoundness(NO_ROUNDNESS);mNotificationParent.updateBackgroundForGroupState();}updateIconVisibilities();updateBackgroundClipping();}@Overridepublic boolean onTouchEvent(MotionEvent event) {if (event.getActionMasked() != MotionEvent.ACTION_DOWN|| !isChildInGroup() || isGroupExpanded()) {return super.onTouchEvent(event);} else {return false;}}@Overrideprotected boolean handleSlideBack() {if (mMenuRow != null && mMenuRow.isMenuVisible()) {animateTranslateNotification(0 /* targetLeft */);return true;}return false;}@Overrideprotected boolean shouldHideBackground() {return super.shouldHideBackground() || mShowNoBackground;}@Overridepublic boolean isSummaryWithChildren() {return mIsSummaryWithChildren;}@Overridepublic boolean areChildrenExpanded() {return mChildrenExpanded;}public List<ExpandableNotificationRow> getAttachedChildren() {return mChildrenContainer == null ? null : mChildrenContainer.getAttachedChildren();}/*** 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<? extends NotificationListItem> 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);}}@Overridepublic boolean isPinned() {return mIsPinned;}@Overridepublic 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<ExpandableNotificationRow> notificationChildren =mChildrenContainer.getAttachedChildren();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();}public void setLongPressListener(LongPressListener longPressListener) {mLongPressListener = longPressListener;}@Overridepublic void setOnClickListener(@Nullable OnClickListener l) {super.setOnClickListener(l);mOnClickListener = l;updateClickAndFocus();}/** The click listener for the bubble button. */public View.OnClickListener getBubbleClickListener() {return new View.OnClickListener() {@Overridepublic void onClick(View v) {Dependency.get(BubbleController.class).onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */);mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */);}};}private void updateClickAndFocus() {boolean normalChild = !isChildInGroup() || isGroupExpanded();boolean clickable = mOnClickListener != null && normalChild;if (isFocusable() != normalChild) {setFocusable(normalChild);}if (isClickable() != clickable) {setClickable(clickable);}}public HeadsUpManager getHeadsUpManager() {return mHeadsUpManager;}public void setGutsView(MenuItem item) {if (getGuts() != null && item.getGutsView() instanceof NotificationGuts.GutsContent) {getGuts().setGutsContent((NotificationGuts.GutsContent) item.getGutsView());}}@Overridepublic 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<MenuItem> items = new ArrayList<>();items.add(NotificationMenuRow.createConversationItem(mContext));items.add(NotificationMenuRow.createPartialConversationItem(mContext));items.add(NotificationMenuRow.createInfoItem(mContext));items.add(NotificationMenuRow.createSnoozeItem(mContext));items.add(NotificationMenuRow.createAppOpsItem(mContext));mMenuRow.setMenuItems(items);}if (existed) {createMenu();}}@Overridepublic void onPluginDisconnected(NotificationMenuRowPlugin plugin) {boolean existed = mMenuRow.getMenuView() != null;mMenuRow = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);if (existed) {createMenu();}}@Overridepublic boolean hasFinishedInitialization() {return getEntry().hasFinishedInitialization();}/*** Get a handle to a NotificationMenuRowPlugin whose menu view has been added to our hierarchy,* or null if there is no menu row** @return a {@link NotificationMenuRowPlugin}, or null*/@Nullablepublic NotificationMenuRowPlugin createMenu() {if (mMenuRow == null) {return null;}if (mMenuRow.getMenuView() == null) {mMenuRow.createMenu(this, mEntry.getSbn());mMenuRow.setAppName(mAppName);FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);addView(mMenuRow.getMenuView(), MENU_VIEW_INDEX, lp);}return mMenuRow;}@Nullablepublic NotificationMenuRowPlugin getProvider() {return mMenuRow;}@Overridepublic void onDensityOrFontScaleChanged() {super.onDensityOrFontScaleChanged();initDimens();initBackground();reInflateViews();}private void reInflateViews() {// 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.getSbn());}if (mGuts != null) {NotificationGuts oldGuts = mGuts;int index = indexOfChild(oldGuts);removeView(oldGuts);mGuts = (NotificationGuts) LayoutInflater.from(mContext).inflate(R.layout.notification_guts, this, false);mGuts.setVisibility(oldGuts.isExposed() ? VISIBLE : GONE);addView(mGuts, index);}View oldMenu = mMenuRow == null ? null : mMenuRow.getMenuView();if (oldMenu != null) {int menuIndex = indexOfChild(oldMenu);removeView(oldMenu);mMenuRow.createMenu(ExpandableNotificationRow.this, mEntry.getSbn());mMenuRow.setAppName(mAppName);addView(mMenuRow.getMenuView(), menuIndex);}for (NotificationContentView l : mLayouts) {l.initView();l.reInflateViews();}mEntry.getSbn().clearPackageContext();// TODO: Move content inflation logic out of this callRowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);params.setNeedsReinflation(true);mRowContentBindStage.requestRebind(mEntry, null /* callback */);}@Overridepublic void onConfigurationChanged(Configuration newConfig) {if (mMenuRow != null && mMenuRow.getMenuView() != null) {mMenuRow.onConfigurationChanged();}if (mImageResolver != null) {mImageResolver.updateMaxImageSizes();}}public void onUiModeChanged() {mUpdateBackgroundOnUpdate = true;reInflateViews();if (mChildrenContainer != null) {for (ExpandableNotificationRow child : mChildrenContainer.getAttachedChildren()) {child.onUiModeChanged();}}}public void setContentBackground(int customBackgroundColor, boolean animate,NotificationContentView notificationContentView) {if (getShowingLayout() == notificationContentView) {setTintColor(customBackgroundColor, animate);}}@Overrideprotected void setBackgroundTintColor(int color) {super.setBackgroundTintColor(color);NotificationContentView view = getShowingLayout();if (view != null) {view.setBackgroundTintColor(color);}}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;}public void updateNotificationColor() {Configuration currentConfig = getResources().getConfiguration();boolean nightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)== Configuration.UI_MODE_NIGHT_YES;mNotificationColor = ContrastColorUtil.resolveContrastColor(mContext,mEntry.getSbn().getNotification().color,getBackgroundColorWithoutTint(), nightMode);}public HybridNotificationView getSingleLineView() {return mPrivateLayout.getSingleLineView();}public boolean isOnKeyguard() {return mOnKeyguard;}public void removeAllChildren() {List<ExpandableNotificationRow> notificationChildren =mChildrenContainer.getAttachedChildren();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);}onAttachedChildrenCountChanged();}@Overridepublic View getView() {return this;}public void setForceUnlocked(boolean forceUnlocked) {mForceUnlocked = forceUnlocked;if (mIsSummaryWithChildren) {List<ExpandableNotificationRow> notificationChildren = getAttachedChildren();for (ExpandableNotificationRow child : notificationChildren) {child.setForceUnlocked(forceUnlocked);}}}@Overridepublic void dismiss(boolean refocusOnDismiss) {super.dismiss(refocusOnDismiss);setLongPressListener(null);mGroupParentWhenDismissed = mNotificationParent;mChildAfterViewWhenDismissed = null;mEntry.getIcons().getStatusBarIcon().setDismissed();if (isChildInGroup()) {List<ExpandableNotificationRow> notificationChildren =mNotificationParent.getAttachedChildren();int i = notificationChildren.indexOf(this);if (i != -1 && i < notificationChildren.size() - 1) {mChildAfterViewWhenDismissed = notificationChildren.get(i + 1);}}}public boolean keepInParent() {return mKeepInParent;}public void setKeepInParent(boolean keepInParent) {mKeepInParent = keepInParent;}@Overridepublic boolean isRemoved() {return mRemoved;}public void setRemoved() {mRemoved = true;mTranslationWhenRemoved = getTranslationY();mWasChildInGroupWhenRemoved = isChildInGroup();if (isChildInGroup()) {mTranslationWhenRemoved += getNotificationParent().getTranslationY();}for (NotificationContentView l : mLayouts) {l.setRemoved();}}public boolean wasChildInGroupWhenRemoved() {return mWasChildInGroupWhenRemoved;}public float getTranslationWhenRemoved() {return mTranslationWhenRemoved;}public NotificationChildrenContainer getChildrenContainer() {return mChildrenContainer;}public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {boolean wasAboveShelf = isAboveShelf();boolean changed = headsUpAnimatingAway != mHeadsupDisappearRunning;mHeadsupDisappearRunning = headsUpAnimatingAway;mPrivateLayout.setHeadsUpAnimatingAway(headsUpAnimatingAway);if (changed && mHeadsUpAnimatingAwayListener != null) {mHeadsUpAnimatingAwayListener.accept(headsUpAnimatingAway);}if (isAboveShelf() != wasAboveShelf) {mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);}}public void setHeadsUpAnimatingAwayListener(Consumer<Boolean> listener) {mHeadsUpAnimatingAwayListener = listener;}/*** @return if the view was just heads upped and is now animating away. During such a time the* layout needs to be kept consistent*/@Overridepublic boolean isHeadsUpAnimatingAway() {return mHeadsupDisappearRunning;}public View getChildAfterViewWhenDismissed() {return mChildAfterViewWhenDismissed;}public View getGroupParentWhenDismissed() {return mGroupParentWhenDismissed;}/*** Dismisses the notification with the option of showing the blocking helper in-place if we have* a negative user sentiment.** @param fromAccessibility whether this dismiss is coming from an accessibility action* @return whether a blocking helper is shown in this row*/public boolean performDismissWithBlockingHelper(boolean fromAccessibility) {NotificationBlockingHelperManager manager =Dependency.get(NotificationBlockingHelperManager.class);boolean isBlockingHelperShown = manager.perhapsShowBlockingHelper(this, mMenuRow);Dependency.get(MetricsLogger.class).count(NotificationCounters.NOTIFICATION_DISMISSED, 1);// Continue with dismiss since we don't want the blocking helper to be directly associated// with a certain notification.performDismiss(fromAccessibility);return isBlockingHelperShown;}public void performDismiss(boolean fromAccessibility) {if (isOnlyChildInGroup()) {NotificationEntry groupSummary =mGroupManager.getLogicalGroupSummary(mEntry.getSbn());if (groupSummary.isClearable()) {// If this is the only child in the group, dismiss the group, but don't try to show// the blocking helper affordance!groupSummary.getRow().performDismiss(fromAccessibility);}}dismiss(fromAccessibility);if (mEntry.isClearable()) {// TODO: beverlyt, log dismissal// TODO: track dismiss sentimentif (mOnDismissRunnable != null) {mOnDismissRunnable.run();}}}public void setBlockingHelperShowing(boolean isBlockingHelperShowing) {mIsBlockingHelperShowing = isBlockingHelperShowing;}public boolean isBlockingHelperShowing() {return mIsBlockingHelperShowing;}public boolean isBlockingHelperShowingAndTranslationFinished() {return mIsBlockingHelperShowing && mNotificationTranslationFinished;}void setOnDismissRunnable(Runnable onDismissRunnable) {mOnDismissRunnable = onDismissRunnable;}@Overridepublic View getShelfTransformationTarget() {if (mIsSummaryWithChildren && !shouldShowPublic()) {return mChildrenContainer.getVisibleHeader().getIcon();}return getShowingLayout().getShelfTransformationTarget();}/*** @return whether the notification is currently showing a view with an icon.*/public boolean isShowingIcon() {if (areGutsExposed()) {return false;}return getShelfTransformationTarget() != null;}/*** Set the icons to be visible of this notification.*/public void setShelfIconVisible(boolean iconVisible) {if (iconVisible != mShelfIconVisible) {mShelfIconVisible = iconVisible;updateIconVisibilities();}}@Overrideprotected void onBelowSpeedBumpChanged() {updateIconVisibilities();}@Overrideprotected void updateContentTransformation() {if (mExpandAnimationRunning) {return;}super.updateContentTransformation();}@Overrideprotected void applyContentTransformation(float contentAlpha, float translationY) {super.applyContentTransformation(contentAlpha, translationY);if (!mIsLastChild) {// Don't fade views unless we're lastcontentAlpha = 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() {// The shelficon is never hidden for children in groupsboolean visible = !isChildInGroup() && mShelfIconVisible;for (NotificationContentView l : mLayouts) {l.setShelfIconVisible(visible);}if (mChildrenContainer != null) {mChildrenContainer.setShelfIconVisible(visible);}}public void setIsLowPriority(boolean isLowPriority) {mIsLowPriority = isLowPriority;mPrivateLayout.setIsLowPriority(isLowPriority);if (mChildrenContainer != null) {mChildrenContainer.setIsLowPriority(isLowPriority);}}public boolean isLowPriority() {return mIsLowPriority;}public void setUsesIncreasedCollapsedHeight(boolean use) {mUseIncreasedCollapsedHeight = use;}public void setUsesIncreasedHeadsUpHeight(boolean use) {mUseIncreasedHeadsUpHeight = use;}public void setNeedsRedaction(boolean needsRedaction) {// TODO: Move inflation logic out of this call and remove this methodif (mNeedsRedaction != needsRedaction) {mNeedsRedaction = needsRedaction;if (!isRemoved()) {RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);if (needsRedaction) {params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);} else {params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);}mRowContentBindStage.requestRebind(mEntry, null /* callback */);}}}public interface ExpansionLogger {void logNotificationExpansion(String key, boolean userAction, boolean expanded);}public ExpandableNotificationRow(Context context, AttributeSet attrs) {super(context, attrs);mImageResolver = new NotificationInlineImageResolver(context,new NotificationInlineImageCache());initDimens();}/*** Initialize row.*/public void initialize(String appName,String notificationKey,ExpansionLogger logger,KeyguardBypassController bypassController,NotificationGroupManager groupManager,HeadsUpManager headsUpManager,RowContentBindStage rowContentBindStage,OnExpandClickListener onExpandClickListener,NotificationMediaManager notificationMediaManager,OnAppOpsClickListener onAppOpsClickListener,FalsingManager falsingManager,StatusBarStateController statusBarStateController,PeopleNotificationIdentifier peopleNotificationIdentifier) {mAppName = appName;if (mMenuRow == null) {mMenuRow = new NotificationMenuRow(mContext, peopleNotificationIdentifier);}if (mMenuRow.getMenuView() != null) {mMenuRow.setAppName(mAppName);}mLogger = logger;mLoggingKey = notificationKey;mBypassController = bypassController;mGroupManager = groupManager;mPrivateLayout.setGroupManager(groupManager);mHeadsUpManager = headsUpManager;mRowContentBindStage = rowContentBindStage;mOnExpandClickListener = onExpandClickListener;mMediaManager = notificationMediaManager;setAppOpsOnClickListener(onAppOpsClickListener);mFalsingManager = falsingManager;mStatusbarStateController = statusBarStateController;mPeopleNotificationIdentifier = peopleNotificationIdentifier;for (NotificationContentView l : mLayouts) {l.setPeopleNotificationIdentifier(mPeopleNotificationIdentifier);}}private void initDimens() {mNotificationMinHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,R.dimen.notification_min_height_legacy);mNotificationMinHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,R.dimen.notification_min_height_before_p);mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext,R.dimen.notification_min_height);mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,R.dimen.notification_min_height_increased);mNotificationMinHeightMedia = NotificationUtils.getFontScaledHeight(mContext,R.dimen.notification_min_height_media);mNotificationMaxHeight = NotificationUtils.getFontScaledHeight(mContext,R.dimen.notification_max_height);mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,R.dimen.notification_max_heads_up_height_legacy);mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,R.dimen.notification_max_heads_up_height_before_p);mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,R.dimen.notification_max_heads_up_height);mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,R.dimen.notification_max_heads_up_height_increased);Resources res = getResources();mIncreasedPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height_increased);mEnableNonGroupedNotificationExpand =res.getBoolean(R.bool.config_enableNonGroupedNotificationExpand);mShowGroupBackgroundWhenExpanded =res.getBoolean(R.bool.config_showGroupNotificationBgWhenExpanded);}NotificationInlineImageResolver getImageResolver() {return mImageResolver;}/*** Resets this view so it can be re-used for an updated notification.*/public void reset() {mShowingPublicInitialized = false;unDismiss();if (mMenuRow == null || !mMenuRow.isMenuVisible()) {resetTranslation();}onHeightReset();requestLayout();}public void showAppOpsIcons(ArraySet<Integer> activeOps) {if (mIsSummaryWithChildren) {mChildrenContainer.showAppOpsIcons(activeOps);}mPrivateLayout.showAppOpsIcons(activeOps);mPublicLayout.showAppOpsIcons(activeOps);}/** Sets the last time the notification being displayed audibly alerted the user. */public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {if (NotificationUtils.useNewInterruptionModel(mContext)) {long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs;boolean alertedRecently =timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS;applyAudiblyAlertedRecently(alertedRecently);removeCallbacks(mExpireRecentlyAlertedFlag);if (alertedRecently) {long timeUntilNoLongerRecent =RECENTLY_ALERTED_THRESHOLD_MS - timeSinceAlertedAudibly;postDelayed(mExpireRecentlyAlertedFlag, timeUntilNoLongerRecent);}}}private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false);private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) {if (mIsSummaryWithChildren) {mChildrenContainer.setRecentlyAudiblyAlerted(audiblyAlertedRecently);}mPrivateLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);}public View.OnClickListener getAppOpsOnClickListener() {return mOnAppOpsClickListener;}void setAppOpsOnClickListener(ExpandableNotificationRow.OnAppOpsClickListener l) {mOnAppOpsClickListener = v -> {createMenu();NotificationMenuRowPlugin provider = getProvider();if (provider == null) {return;}MenuItem menuItem = provider.getAppOpsMenuItem(mContext);if (menuItem != null) {l.onClick(this, v.getWidth() / 2, v.getHeight() / 2, menuItem);}};}@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic void onInflate(ViewStub stub, View inflated) {mChildrenContainer = (NotificationChildrenContainer) inflated;mChildrenContainer.setIsLowPriority(mIsLowPriority);mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);mChildrenContainer.onNotificationUpdated();if (mShouldTranslateContents) {mTranslateableViews.add(mChildrenContainer);}}});if (mShouldTranslateContents) {// Add the views that we translate to reveal the menumTranslateableViews = new ArrayList<>();for (int i = 0; i < getChildCount(); i++) {mTranslateableViews.add(getChildAt(i));}// Remove views that don't translatemTranslateableViews.remove(mChildrenContainerStub);mTranslateableViews.remove(mGutsStub);}}private void doLongClickCallback() {doLongClickCallback(getWidth() / 2, getHeight() / 2);}public void doLongClickCallback(int x, int y) {createMenu();NotificationMenuRowPlugin provider = getProvider();MenuItem menuItem = null;if (provider != null) {menuItem = provider.getLongpressMenuItem(mContext);}doLongClickCallback(x, y, menuItem);}private void doLongClickCallback(int x, int y, MenuItem menuItem) {if (mLongPressListener != null && menuItem != null) {mLongPressListener.onLongPress(this, x, y, menuItem);}}@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {if (KeyEvent.isConfirmKey(keyCode)) {event.startTracking();return true;}return super.onKeyDown(keyCode, event);}@Overridepublic boolean onKeyUp(int keyCode, KeyEvent event) {if (KeyEvent.isConfirmKey(keyCode)) {if (!event.isCanceled()) {performClick();}return true;}return super.onKeyUp(keyCode, event);}@Overridepublic boolean onKeyLongPress(int keyCode, KeyEvent event) {if (KeyEvent.isConfirmKey(keyCode)) {doLongClickCallback();return true;}return false;}public void resetTranslation() {if (mTranslateAnim != null) {mTranslateAnim.cancel();}if (!mShouldTranslateContents) {setTranslationX(0);} else if (mTranslateableViews != null) {for (int i = 0; i < mTranslateableViews.size(); i++) {mTranslateableViews.get(i).setTranslationX(0);}invalidateOutline();getEntry().getIcons().getShelfIcon().setScrollX(0);}if (mMenuRow != null) {mMenuRow.resetMenu();}}void onGutsOpened() {resetTranslation();updateContentAccessibilityImportanceForGuts(false /* isEnabled */);}void onGutsClosed() {updateContentAccessibilityImportanceForGuts(true /* isEnabled */);}/*** Updates whether all the non-guts content inside this row is important for accessibility.** @param isEnabled whether the content views should be enabled for accessibility*/private void updateContentAccessibilityImportanceForGuts(boolean isEnabled) {if (mChildrenContainer != null) {updateChildAccessibilityImportance(mChildrenContainer, isEnabled);}if (mLayouts != null) {for (View view : mLayouts) {updateChildAccessibilityImportance(view, isEnabled);}}if (isEnabled) {this.requestAccessibilityFocus();}}/*** Updates whether the given childView is important for accessibility based on* {@code isEnabled}.*/private void updateChildAccessibilityImportance(View childView, boolean isEnabled) {childView.setImportantForAccessibility(isEnabled? View.IMPORTANT_FOR_ACCESSIBILITY_AUTO: View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);}public CharSequence getActiveRemoteInputText() {return mPrivateLayout.getActiveRemoteInputText();}public void animateTranslateNotification(final float leftTarget) {if (mTranslateAnim != null) {mTranslateAnim.cancel();}mTranslateAnim = getTranslateViewAnimator(leftTarget, null /* updateListener */);if (mTranslateAnim != null) {mTranslateAnim.start();}}@Overridepublic void setTranslation(float translationX) {if (isBlockingHelperShowingAndTranslationFinished()) {mGuts.setTranslationX(translationX);return;} else if (!mShouldTranslateContents) {setTranslationX(translationX);} else if (mTranslateableViews != null) {// Translate the group of viewsfor (int i = 0; i < mTranslateableViews.size(); i++) {if (mTranslateableViews.get(i) != null) {mTranslateableViews.get(i).setTranslationX(translationX);}}invalidateOutline();// In order to keep the shelf in sync with this swiping, we're simply translating// it's icon by the same amount. The translation is already being used for the normal// positioning, so we can use the scrollX instead.getEntry().getIcons().getShelfIcon().setScrollX((int) -translationX);}if (mMenuRow != null && mMenuRow.getMenuView() != null) {mMenuRow.onParentTranslationUpdate(translationX);}}@Overridepublic float getTranslation() {if (!mShouldTranslateContents) {return getTranslationX();}if (isBlockingHelperShowingAndCanTranslate()) {return mGuts.getTranslationX();}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;}private boolean isBlockingHelperShowingAndCanTranslate() {return areGutsExposed() && mIsBlockingHelperShowing && mNotificationTranslationFinished;}public Animator getTranslateViewAnimator(final float leftTarget,AnimatorUpdateListener listener) {if (mTranslateAnim != null) {mTranslateAnim.cancel();}final ObjectAnimator translateAnim = ObjectAnimator.ofFloat(this, TRANSLATE_CONTENT,leftTarget);if (listener != null) {translateAnim.addUpdateListener(listener);}translateAnim.addListener(new AnimatorListenerAdapter() {boolean cancelled = false;@Overridepublic void onAnimationCancel(Animator anim) {cancelled = true;}@Overridepublic void onAnimationEnd(Animator anim) {if (mIsBlockingHelperShowing) {mNotificationTranslationFinished = true;}if (!cancelled && leftTarget == 0) {if (mMenuRow != null) {mMenuRow.resetMenu();}mTranslateAnim = null;}}});mTranslateAnim = translateAnim;return translateAnim;}void ensureGutsInflated() {if (mGuts == null) {mGutsStub.inflate();}}private void updateChildrenVisibility() {boolean hideContentWhileLaunching = mExpandAnimationRunning && mGuts != null&& mGuts.isExposed();mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren&& !hideContentWhileLaunching ? VISIBLE : INVISIBLE);if (mChildrenContainer != null) {mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren&& !hideContentWhileLaunching ? VISIBLE: INVISIBLE);}// The limits might have changed if the view suddenly became a group or vice versaupdateLimits();}@Overridepublic 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;}public void applyExpandAnimationParams(ExpandAnimationParameters params) {if (params == null) {return;}float zProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(params.getProgress(0, 50));float translationZ = MathUtils.lerp(params.getStartTranslationZ(),mNotificationLaunchHeight,zProgress);setTranslationZ(translationZ);float extraWidthForClipping = params.getWidth() - getWidth()+ MathUtils.lerp(0, mOutlineRadius * 2, params.getProgress());setExtraWidthForClipping(extraWidthForClipping);int top = params.getTop();float interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(params.getProgress());int startClipTopAmount = params.getStartClipTopAmount();if (mNotificationParent != null) {float parentY = mNotificationParent.getTranslationY();top -= parentY;mNotificationParent.setTranslationZ(translationZ);int parentStartClipTopAmount = params.getParentStartClipTopAmount();if (startClipTopAmount != 0) {int clipTopAmount = (int) MathUtils.lerp(parentStartClipTopAmount,parentStartClipTopAmount - startClipTopAmount,interpolation);mNotificationParent.setClipTopAmount(clipTopAmount);}mNotificationParent.setExtraWidthForClipping(extraWidthForClipping);float clipBottom = Math.max(params.getBottom(),parentY + mNotificationParent.getActualHeight()- mNotificationParent.getClipBottomAmount());float clipTop = Math.min(params.getTop(), parentY);int minimumHeightForClipping = (int) (clipBottom - clipTop);mNotificationParent.setMinimumHeightForClipping(minimumHeightForClipping);} else if (startClipTopAmount != 0) {int clipTopAmount = (int) MathUtils.lerp(startClipTopAmount, 0, interpolation);setClipTopAmount(clipTopAmount);}setTranslationY(top);setActualHeight(params.getHeight());mBackgroundNormal.setExpandAnimationParams(params);}public void setExpandAnimationRunning(boolean expandAnimationRunning) {View contentView;if (mIsSummaryWithChildren) {contentView =  mChildrenContainer;} else {contentView = getShowingLayout();}if (mGuts != null && mGuts.isExposed()) {contentView = mGuts;}if (expandAnimationRunning) {contentView.animate().alpha(0f).setDuration(ActivityLaunchAnimator.ANIMATION_DURATION_FADE_CONTENT).setInterpolator(Interpolators.ALPHA_OUT);setAboveShelf(true);mExpandAnimationRunning = true;getViewState().cancelAnimations(this);mNotificationLaunchHeight = AmbientState.getNotificationLaunchHeight(getContext());} else {mExpandAnimationRunning = false;setAboveShelf(isAboveShelf());if (mGuts != null) {mGuts.setAlpha(1.0f);}if (contentView != null) {contentView.setAlpha(1.0f);}setExtraWidthForClipping(0.0f);if (mNotificationParent != null) {mNotificationParent.setExtraWidthForClipping(0.0f);mNotificationParent.setMinimumHeightForClipping(0);}}if (mNotificationParent != null) {mNotificationParent.setChildIsExpanding(mExpandAnimationRunning);}updateChildrenVisibility();updateClipping();mBackgroundNormal.setExpandAnimationRunning(expandAnimationRunning);}private void setChildIsExpanding(boolean isExpanding) {mChildIsExpanding = isExpanding;updateClipping();invalidate();}@Overridepublic boolean hasExpandingChild() {return mChildIsExpanding;}@Overridepublic @NonNull StatusBarIconView getShelfIcon() {return getEntry().getIcons().getShelfIcon();}@Overrideprotected boolean shouldClipToActualHeight() {return super.shouldClipToActualHeight() && !mExpandAnimationRunning;}@Overridepublic boolean isExpandAnimationRunning() {return mExpandAnimationRunning;}/*** Tap sounds should not be played when we're unlocking.* Doing so would cause audio collision and the system would feel unpolished.*/@Overridepublic boolean isSoundEffectsEnabled() {final boolean mute = mStatusbarStateController != null&& mStatusbarStateController.isDozing()&& mSecureStateProvider != null &&!mSecureStateProvider.getAsBoolean();return !mute && super.isSoundEffectsEnabled();}public boolean isExpandable() {if (mIsSummaryWithChildren && !shouldShowPublic()) {return !mChildrenExpanded;}return mEnableNonGroupedNotificationExpand && mExpandable;}public void setExpandable(boolean expandable) {mExpandable = expandable;mPrivateLayout.updateExpandButtons(isExpandable());}@Overridepublic 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 && !shouldShowPublic() && allowChildExpansion&& !mChildrenContainer.showingAsLowPriority()) {final boolean wasExpanded = mGroupManager.isGroupExpanded(mEntry.getSbn());mGroupManager.setGroupExpanded(mEntry.getSbn(), userExpanded);onExpansionChanged(true /* userAction */, wasExpanded);return;}if (userExpanded && !mExpandable) return;final boolean wasExpanded = isExpanded();mHasUserChangedExpansion = true;mUserExpanded = userExpanded;onExpansionChanged(true /* userAction */, wasExpanded);if (!wasExpanded && isExpanded()&& getActualHeight() != getIntrinsicHeight()) {notifyHeightChanged(true /* needsAnimation */);}}public void resetUserExpansion() {boolean wasExpanded = isExpanded();mHasUserChangedExpansion = false;mUserExpanded = false;if (wasExpanded != isExpanded()) {if (mIsSummaryWithChildren) {mChildrenContainer.onExpansionChanged();}notifyHeightChanged(false /* needsAnimation */);}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) {boolean wasAboveShelf = isAboveShelf();final boolean wasExpanded = isExpanded();mOnKeyguard = onKeyguard;onExpansionChanged(false /* userAction */, wasExpanded);if (wasExpanded != isExpanded()) {if (mIsSummaryWithChildren) {mChildrenContainer.updateGroupOverflow();}notifyHeightChanged(false /* needsAnimation */);}if (isAboveShelf() != wasAboveShelf) {mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);}}updateRippleAllowed();}private void updateRippleAllowed() {boolean allowed = isOnKeyguard()|| mEntry.getSbn().getNotification().contentIntent == null;setRippleAllowed(allowed);}@Overridepublic 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) {return mChildrenContainer.getIntrinsicHeight();} else if (canShowHeadsUp() && isHeadsUpState()) {if (isPinned() || mHeadsupDisappearRunning) {return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);} else if (isExpanded()) {return Math.max(getMaxExpandHeight(), getHeadsUpHeight());} else {return Math.max(getCollapsedHeight(), getHeadsUpHeight());}} else if (isExpanded()) {return getMaxExpandHeight();} else {return getCollapsedHeight();}}/*** @return {@code true} if the notification can show it's heads up layout. This is mostly true*         except for legacy use cases.*/public boolean canShowHeadsUp() {if (mOnKeyguard && !isDozing() && !isBypassEnabled()) {return false;}return true;}private boolean isBypassEnabled() {return mBypassController == null || mBypassController.getBypassEnabled();}private boolean isDozing() {return mStatusbarStateController != null && mStatusbarStateController.isDozing();}@Overridepublic boolean isGroupExpanded() {return mGroupManager.isGroupExpanded(mEntry.getSbn());}private void onAttachedChildrenCountChanged() {mIsSummaryWithChildren = mChildrenContainer != null&& mChildrenContainer.getNotificationChildCount() > 0;if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation());}getShowingLayout().updateBackgroundColor(false /* animate */);mPrivateLayout.updateExpandButtons(isExpandable());updateChildrenHeaderAppearance();updateChildrenVisibility();applyChildrenRoundness();}protected void expandNotification() {mExpandClickListener.onClick(this);}/*** Returns the number of channels covered by the notification row (including its children if* it's a summary notification).*/public int getNumUniqueChannels() {return getUniqueChannels().size();}/*** Returns the channels covered by the notification row (including its children if* it's a summary notification).*/public ArraySet<NotificationChannel> getUniqueChannels() {ArraySet<NotificationChannel> channels = new ArraySet<>();channels.add(mEntry.getChannel());// If this is a summary, then add in the children notification channels for the// same user and pkg.if (mIsSummaryWithChildren) {final List<ExpandableNotificationRow> childrenRows = getAttachedChildren();final int numChildren = childrenRows.size();for (int i = 0; i < numChildren; i++) {final ExpandableNotificationRow childRow = childrenRows.get(i);final NotificationChannel childChannel = childRow.getEntry().getChannel();final StatusBarNotification childSbn = childRow.getEntry().getSbn();if (childSbn.getUser().equals(mEntry.getSbn().getUser())&& childSbn.getPackageName().equals(mEntry.getSbn().getPackageName())) {channels.add(childChannel);}}}return channels;}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;}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {int intrinsicBefore = getIntrinsicHeight();super.onLayout(changed, left, top, right, bottom);if (intrinsicBefore != getIntrinsicHeight() && intrinsicBefore != 0) {notifyHeightChanged(true  /* needsAnimation */);}if (mMenuRow != null && mMenuRow.getMenuView() != null) {mMenuRow.onParentHeightUpdate();}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 = mContentShift;}}@Overrideprotected float getContentTransformationShift() {return mIconTransformContentShift;}@Overridepublic void notifyHeightChanged(boolean needsAnimation) {super.notifyHeightChanged(needsAnimation);getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked());}public void setSensitive(boolean sensitive, boolean hideSensitive) {mSensitive = sensitive;mSensitiveHiddenInGeneral = hideSensitive;}@Overridepublic void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {mHideSensitiveForIntrinsicHeight = hideSensitive;if (mIsSummaryWithChildren) {List<ExpandableNotificationRow> notificationChildren =mChildrenContainer.getAttachedChildren();for (int i = 0; i < notificationChildren.size(); i++) {ExpandableNotificationRow child = notificationChildren.get(i);child.setHideSensitiveForIntrinsicHeight(hideSensitive);}}}@Overridepublic void setHideSensitive(boolean hideSensitive, boolean animated, long delay,long duration) {if (getVisibility() == GONE) {// If we are GONE, the hideSensitive parameter will not be calculated and always be// false, which is incorrect, let's wait until a real call comes in later.return;}boolean oldShowingPublic = mShowingPublic;mShowingPublic = mSensitive && hideSensitive;if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {return;}// bail out if no public versionif (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, mShowingPublic);}NotificationContentView showingLayout = getShowingLayout();showingLayout.updateBackgroundColor(animated);mPrivateLayout.updateExpandButtons(isExpandable());updateShelfIconColor();mShowingPublicInitialized = true;}private void animateShowingPublic(long delay, long duration, boolean showingPublic) {View[] privateViews = mIsSummaryWithChildren? new View[] {mChildrenContainer}: new View[] {mPrivateLayout};View[] publicViews = new View[] {mPublicLayout};View[] hiddenChildren = showingPublic ? privateViews : publicViews;View[] shownChildren = showingPublic ? 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() {@Overridepublic 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);}}@Overridepublic boolean mustStayOnScreen() {return mIsHeadsUp && mMustStayOnScreen;}/*** @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 NotificationEntry#isClearable()}.*/public boolean canViewBeDismissed() {return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);}private boolean shouldShowPublic() {return mSensitive && mHideSensitiveForIntrinsicHeight;}public void makeActionsVisibile() {setUserExpanded(true, true);if (isChildInGroup()) {mGroupManager.setGroupExpanded(mEntry.getSbn(), 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 mPrivateLayout.getExpandHeight();}private int getHeadsUpHeight() {return getShowingLayout().getHeadsUpHeight(false /* forceNoHeader */);}public boolean areGutsExposed() {return (mGuts != null && mGuts.isExposed());}@Overridepublic boolean isContentExpandable() {if (mIsSummaryWithChildren && !shouldShowPublic()) {return true;}NotificationContentView showingLayout = getShowingLayout();return showingLayout.isContentExpandable();}@Overrideprotected View getContentView() {if (mIsSummaryWithChildren && !shouldShowPublic()) {return mChildrenContainer;}return getShowingLayout();}@Overridepublic long performRemoveAnimation(long duration, long delay, float translationDirection,boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable,AnimatorListenerAdapter animationListener) {if (mMenuRow != null && mMenuRow.isMenuVisible()) {Animator anim = getTranslateViewAnimator(0f, null /* listener */);if (anim != null) {anim.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {ExpandableNotificationRow.super.performRemoveAnimation(duration, delay, translationDirection, isHeadsUpAnimation,endLocation, onFinishedRunnable, animationListener);}});anim.start();return anim.getDuration();}}return super.performRemoveAnimation(duration, delay, translationDirection,isHeadsUpAnimation, endLocation, onFinishedRunnable, animationListener);}@Overrideprotected 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 resetif (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);}} else {setHeadsUpAnimatingAway(false);}}@Overridepublic int getExtraBottomPadding() {if (mIsSummaryWithChildren && isGroupExpanded()) {return mIncreasedPaddingBetweenElements;}return 0;}@Overridepublic 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);}if (mMenuRow != null && mMenuRow.getMenuView() != null) {mMenuRow.onParentHeightUpdate();}handleIntrinsicHeightReached();}@Overridepublic int getMaxContentHeight() {if (mIsSummaryWithChildren && !shouldShowPublic()) {return mChildrenContainer.getMaxContentHeight();}NotificationContentView showingLayout = getShowingLayout();return showingLayout.getMaxHeight();}@Overridepublic int getMinHeight(boolean ignoreTemporaryStates) {if (!ignoreTemporaryStates && mGuts != null && mGuts.isExposed()) {return mGuts.getIntrinsicHeight();} else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp&& mHeadsUpManager.isTrackingHeadsUp()) {return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);} else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) {return mChildrenContainer.getMinHeight();} else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp) {return getHeadsUpHeight();}NotificationContentView showingLayout = getShowingLayout();return showingLayout.getMinHeight();}@Overridepublic int getCollapsedHeight() {if (mIsSummaryWithChildren && !shouldShowPublic()) {return mChildrenContainer.getCollapsedHeight();}return getMinHeight();}@Overridepublic int getHeadsUpHeightWithoutHeader() {if (!canShowHeadsUp() || !mIsHeadsUp) {return getCollapsedHeight();}if (mIsSummaryWithChildren && !shouldShowPublic()) {return mChildrenContainer.getCollapsedHeightWithoutHeader();}return getShowingLayout().getHeadsUpHeight(true /* forceNoHeader */);}@Overridepublic void setClipTopAmount(int clipTopAmount) {super.setClipTopAmount(clipTopAmount);for (NotificationContentView l : mLayouts) {l.setClipTopAmount(clipTopAmount);}if (mGuts != null) {mGuts.setClipTopAmount(clipTopAmount);}}@Overridepublic void setClipBottomAmount(int clipBottomAmount) {if (mExpandAnimationRunning) {return;}if (clipBottomAmount != mClipBottomAmount) {super.setClipBottomAmount(clipBottomAmount);for (NotificationContentView l : mLayouts) {l.setClipBottomAmount(clipBottomAmount);}if (mGuts != null) {mGuts.setClipBottomAmount(clipBottomAmount);}}if (mChildrenContainer != null && !mChildIsExpanding) {// We have to update this even if it hasn't changed, since the children locations can// have changedmChildrenContainer.setClipBottomAmount(clipBottomAmount);}}public NotificationContentView getShowingLayout() {return shouldShowPublic() ? mPublicLayout : mPrivateLayout;}public View getExpandedContentView() {return getPrivateLayout().getExpandedChild();}public void setLegacy(boolean legacy) {for (NotificationContentView l : mLayouts) {l.setLegacy(legacy);}}@Overrideprotected void updateBackgroundTint() {super.updateBackgroundTint();updateBackgroundForGroupState();if (mIsSummaryWithChildren) {List<ExpandableNotificationRow> notificationChildren =mChildrenContainer.getAttachedChildren();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 = !mShowGroupBackgroundWhenExpanded && isGroupExpanded()&& !isGroupExpansionChanging() && !isUserLocked();mChildrenContainer.updateHeaderForExpansion(mShowNoBackground);List<ExpandableNotificationRow> children = mChildrenContainer.getAttachedChildren();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 onExpandedByGesture(boolean userExpanded) {int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;if (mGroupManager.isSummaryOfGroup(mEntry.getSbn())) {event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER;}MetricsLogger.action(mContext, event, userExpanded);}@Overridepublic 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;}@Overrideprotected boolean disallowSingleClick(MotionEvent event) {if (areGutsExposed()) {return false;}float x = event.getX();float y = event.getY();NotificationHeaderView header = getVisibleNotificationHeader();if (header != null && header.isInTouchRect(x - getTranslation(), y)) {return true;}if ((!mIsSummaryWithChildren || shouldShowPublic())&& getShowingLayout().disallowSingleClick(x, y)) {return true;}return super.disallowSingleClick(event);}private void onExpansionChanged(boolean userAction, boolean wasExpanded) {boolean nowExpanded = isExpanded();if (mIsSummaryWithChildren && (!mIsLowPriority || wasExpanded)) {nowExpanded = mGroupManager.isGroupExpanded(mEntry.getSbn());}if (nowExpanded != wasExpanded) {updateShelfIconColor();if (mLogger != null) {mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded);}if (mIsSummaryWithChildren) {mChildrenContainer.onExpansionChanged();}if (mExpansionChangedListener != null) {mExpansionChangedListener.onExpansionChanged(nowExpanded);}}}public void setOnExpansionChangedListener(@Nullable OnExpansionChangedListener listener) {mExpansionChangedListener = listener;}/*** Perform an action when the notification height has reached its intrinsic height.** @param runnable the runnable to run*/public void performOnIntrinsicHeightReached(@Nullable Runnable runnable) {mOnIntrinsicHeightReachedRunnable = runnable;handleIntrinsicHeightReached();}private void handleIntrinsicHeightReached() {if (mOnIntrinsicHeightReachedRunnable != null&& getActualHeight() == getIntrinsicHeight()) {mOnIntrinsicHeightReachedRunnable.run();mOnIntrinsicHeightReachedRunnable = null;}}@Overridepublic void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {super.onInitializeAccessibilityNodeInfoInternal(info);info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);if (canViewBeDismissed()) {info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);}boolean expandable = shouldShowPublic();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);}}NotificationMenuRowPlugin provider = getProvider();if (provider != null) {MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext());if (snoozeMenu != null) {AccessibilityAction action = new AccessibilityAction(R.id.action_snooze,getContext().getResources().getString(R.string.notification_menu_snooze_action));info.addAction(action);}}}@Overridepublic boolean performAccessibilityActionInternal(int action, Bundle arguments) {if (super.performAccessibilityActionInternal(action, arguments)) {return true;}switch (action) {case AccessibilityNodeInfo.ACTION_DISMISS:performDismissWithBlockingHelper(true /* fromAccessibility */);return true;case AccessibilityNodeInfo.ACTION_COLLAPSE:case AccessibilityNodeInfo.ACTION_EXPAND:mExpandClickListener.onClick(this);return true;case AccessibilityNodeInfo.ACTION_LONG_CLICK:doLongClickCallback();return true;default:if (action == R.id.action_snooze) {NotificationMenuRowPlugin provider = getProvider();if (provider == null) {return false;}MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext());if (snoozeMenu != null) {doLongClickCallback(getWidth() / 2, getHeight() / 2, snoozeMenu);}return true;}}return false;}public interface OnExpandClickListener {void onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded);}@Overridepublic ExpandableViewState createExpandableViewState() {return new NotificationViewState();}@Overridepublic boolean isAboveShelf() {return (canShowHeadsUp()&& (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf)|| mExpandAnimationRunning || mChildIsExpanding));}@Overridepublic boolean topAmountNeedsClipping() {if (isGroupExpanded()) {return true;}if (isGroupExpansionChanging()) {return true;}if (getShowingLayout().shouldClipToRounding(true /* topRounded */,false /* bottomRounded */)) {return true;}if (mGuts != null && mGuts.getAlpha() != 0.0f) {return true;}return false;}@Overrideprotected boolean childNeedsClipping(View child) {if (child instanceof NotificationContentView) {NotificationContentView contentView = (NotificationContentView) child;if (isClippingNeeded()) {return true;} else if (!hasNoRounding()&& contentView.shouldClipToRounding(getCurrentTopRoundness() != 0.0f,getCurrentBottomRoundness() != 0.0f)) {return true;}} else if (child == mChildrenContainer) {if (isClippingNeeded() || !hasNoRounding()) {return true;}} else if (child instanceof NotificationGuts) {return !hasNoRounding();}return super.childNeedsClipping(child);}@Overrideprotected void applyRoundness() {super.applyRoundness();applyChildrenRoundness();}private void applyChildrenRoundness() {if (mIsSummaryWithChildren) {mChildrenContainer.setCurrentBottomRoundness(getCurrentBottomRoundness());}}@Overridepublic Path getCustomClipPath(View child) {if (child instanceof NotificationGuts) {return getClipPath(true /* ignoreTranslation */);}return super.getCustomClipPath(child);}private boolean hasNoRounding() {return getCurrentBottomRoundness() == 0.0f && getCurrentTopRoundness() == 0.0f;}//TODO: this logic can't depend on layout if we are recycling!public boolean isMediaRow() {return getExpandedContentView() != null&& getExpandedContentView().findViewById(com.android.internal.R.id.media_actions) != null;}public boolean isTopLevelChild() {return getParent() instanceof NotificationStackScrollLayout;}public boolean isGroupNotFullyVisible() {return getClipTopAmount() > 0 || getTranslationY() < 0;}public void setAboveShelf(boolean aboveShelf) {boolean wasAboveShelf = isAboveShelf();mAboveShelf = aboveShelf;if (isAboveShelf() != wasAboveShelf) {mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);}}/** Sets whether dismiss gestures are right-to-left (instead of left-to-right). */public void setDismissRtl(boolean dismissRtl) {if (mMenuRow != null) {mMenuRow.setDismissRtl(dismissRtl);}}private static class NotificationViewState extends ExpandableViewState {@Overridepublic void applyToView(View view) {if (view instanceof ExpandableNotificationRow) {ExpandableNotificationRow row = (ExpandableNotificationRow) view;if (row.isExpandAnimationRunning()) {return;}handleFixedTranslationZ(row);super.applyToView(view);row.applyChildrenState();}}private void handleFixedTranslationZ(ExpandableNotificationRow row) {if (row.hasExpandingChild()) {zTranslation = row.getTranslationZ();clipTopAmount = row.getClipTopAmount();}}@Overrideprotected void onYTranslationAnimationFinished(View view) {super.onYTranslationAnimationFinished(view);if (view instanceof ExpandableNotificationRow) {ExpandableNotificationRow row = (ExpandableNotificationRow) view;if (row.isHeadsUpAnimatingAway()) {row.setHeadsUpAnimatingAway(false);}}}@Overridepublic void animateTo(View child, AnimationProperties properties) {if (child instanceof ExpandableNotificationRow) {ExpandableNotificationRow row = (ExpandableNotificationRow) child;if (row.isExpandAnimationRunning()) {return;}handleFixedTranslationZ(row);super.animateTo(child, properties);row.startChildAnimation(properties);}}}/*** Returns the Smart Suggestions backing the smart suggestion buttons in the notification.*/public SmartRepliesAndActions getExistingSmartRepliesAndActions() {return mPrivateLayout.getCurrentSmartRepliesAndActions();}@VisibleForTestingprotected void setChildrenContainer(NotificationChildrenContainer childrenContainer) {mChildrenContainer = childrenContainer;}@VisibleForTestingprotected void setPrivateLayout(NotificationContentView privateLayout) {mPrivateLayout = privateLayout;}@VisibleForTestingprotected void setPublicLayout(NotificationContentView publicLayout) {mPublicLayout = publicLayout;}/*** Equivalent to View.OnLongClickListener with coordinates*/public interface LongPressListener {/*** Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates* @return whether the longpress was handled*/boolean onLongPress(View v, int x, int y, MenuItem item);}/*** Equivalent to View.OnClickListener with coordinates*/public interface OnAppOpsClickListener {/*** Equivalent to {@link View.OnClickListener#onClick(View)} with coordinates* @return whether the click was handled*/boolean onClick(View v, int x, int y, MenuItem item);}@Overridepublic void dump(FileDescriptor fd, PrintWriter pw, String[] args) {super.dump(fd, pw, args);pw.println("  Notification: " + mEntry.getKey());pw.print("    visibility: " + getVisibility());pw.print(", alpha: " + getAlpha());pw.print(", translation: " + getTranslation());pw.print(", removed: " + isRemoved());pw.print(", expandAnimationRunning: " + mExpandAnimationRunning);NotificationContentView showingLayout = getShowingLayout();pw.print(", privateShowing: " + (showingLayout == mPrivateLayout));pw.println();showingLayout.dump(fd, pw, args);pw.print("    ");if (getViewState() != null) {getViewState().dump(fd, pw, args);} else {pw.print("no viewState!!!");}pw.println();pw.println();if (mIsSummaryWithChildren) {pw.print("  ChildrenContainer");pw.print(" visibility: " + mChildrenContainer.getVisibility());pw.print(", alpha: " + mChildrenContainer.getAlpha());pw.print(", translationY: " + mChildrenContainer.getTranslationY());pw.println();List<ExpandableNotificationRow> notificationChildren = getAttachedChildren();pw.println("  Children: " + notificationChildren.size());pw.println("  {");for(ExpandableNotificationRow child : notificationChildren) {child.dump(fd, pw, args);}pw.println("  }");pw.println();}}/*** Background task for executing IPCs to check if the notification is a system notification. The* output is used for both the blocking helper and the notification info.*/private class SystemNotificationAsyncTask extends AsyncTask<Void, Void, Boolean> {@Overrideprotected Boolean doInBackground(Void... voids) {return isSystemNotification(mContext, mEntry.getSbn());}@Overrideprotected void onPostExecute(Boolean result) {if (mEntry != null) {mEntry.mIsSystemNotification = result;}}}
}

在ExpandableNotificationRowController中通过PluginManagerImpl的addPluginListener方法注册了Plugin的回调(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java):

/*** Controller for {@link ExpandableNotificationRow}.*/
@NotificationRowScope
public class ExpandableNotificationRowController {private final ExpandableNotificationRow mView;private final ActivatableNotificationViewController mActivatableNotificationViewController;private final NotificationMediaManager mMediaManager;private final PluginManager mPluginManager;private final SystemClock mClock;private final String mAppName;private final String mNotificationKey;private final KeyguardBypassController mKeyguardBypassController;private final NotificationGroupManager mNotificationGroupManager;private final RowContentBindStage mRowContentBindStage;private final NotificationLogger mNotificationLogger;private final HeadsUpManager mHeadsUpManager;private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;private final StatusBarStateController mStatusBarStateController;private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =this::logNotificationExpansion;private final ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;private final NotificationGutsManager mNotificationGutsManager;private Runnable mOnDismissRunnable;private final FalsingManager mFalsingManager;private final boolean mAllowLongPress;private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;@Injectpublic ExpandableNotificationRowController(ExpandableNotificationRow view,ActivatableNotificationViewController activatableNotificationViewController,NotificationMediaManager mediaManager, PluginManager pluginManager,SystemClock clock, @AppName String appName, @NotificationKey String notificationKey,KeyguardBypassController keyguardBypassController,NotificationGroupManager notificationGroupManager,RowContentBindStage rowContentBindStage,NotificationLogger notificationLogger, HeadsUpManager headsUpManager,ExpandableNotificationRow.OnExpandClickListener onExpandClickListener,StatusBarStateController statusBarStateController,NotificationGutsManager notificationGutsManager,@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,@DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager,PeopleNotificationIdentifier peopleNotificationIdentifier) {mView = view;mActivatableNotificationViewController = activatableNotificationViewController;mMediaManager = mediaManager;mPluginManager = pluginManager;mClock = clock;mAppName = appName;mNotificationKey = notificationKey;mKeyguardBypassController = keyguardBypassController;mNotificationGroupManager = notificationGroupManager;mRowContentBindStage = rowContentBindStage;mNotificationLogger = notificationLogger;mHeadsUpManager = headsUpManager;mOnExpandClickListener = onExpandClickListener;mStatusBarStateController = statusBarStateController;mNotificationGutsManager = notificationGutsManager;mOnDismissRunnable = onDismissRunnable;mOnAppOpsClickListener = mNotificationGutsManager::openGuts;mAllowLongPress = allowLongPress;mFalsingManager = falsingManager;mPeopleNotificationIdentifier = peopleNotificationIdentifier;}/*** Initialize the controller.*/public void init() {mActivatableNotificationViewController.init();mView.initialize(mAppName,mNotificationKey,mExpansionLogger,mKeyguardBypassController,mNotificationGroupManager,mHeadsUpManager,mRowContentBindStage,mOnExpandClickListener,mMediaManager,mOnAppOpsClickListener,mFalsingManager,mStatusBarStateController,mPeopleNotificationIdentifier);mView.setOnDismissRunnable(mOnDismissRunnable);mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);if (mAllowLongPress) {mView.setLongPressListener((v, x, y, item) -> {if (mView.isSummaryWithChildren()) {mView.expandNotification();return true;}return mNotificationGutsManager.openGuts(v, x, y, item);});}if (ENABLE_REMOTE_INPUT) {mView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);}mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {@Overridepublic void onViewAttachedToWindow(View v) {mView.getEntry().setInitializationTime(mClock.elapsedRealtime());mPluginManager.addPluginListener(mView,NotificationMenuRowPlugin.class, false /* Allow multiple */);}@Overridepublic void onViewDetachedFromWindow(View v) {mPluginManager.removePluginListener(mView);}});}private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {mNotificationLogger.onExpansionChanged(key, userAction, expanded);}/** */public void setOnDismissRunnable(Runnable onDismissRunnable) {mOnDismissRunnable = onDismissRunnable;mView.setOnDismissRunnable(onDismissRunnable);}
}

注册回调流程

从PluginManagerImpl的addPluginListener方法开始(frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java):

    public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) {addPluginListener(listener, cls, false);}public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,boolean allowMultiple) {addPluginListener(PluginManager.Helper.getAction(cls), listener, cls, allowMultiple);}public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,Class<?> cls) {addPluginListener(action, listener, cls, false);}public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,Class cls, boolean allowMultiple) {mPluginPrefs.addAction(action);PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,allowMultiple, mLooper, cls, this);p.loadAll();synchronized (this) {mPluginMap.put(listener, p);}startListening();}

调用了PluginInstanceManager的loadAll方法(frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java):

    public void loadAll() {if (DEBUG) Log.d(TAG, "startListening");mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL);}

PluginHandler是PluginInstanceManager的内部类,其handleMessage方法:

        @Overridepublic void handleMessage(Message msg) {switch (msg.what) {case QUERY_ALL:if (DEBUG) Log.d(TAG, "queryAll " + mAction);for (int i = mPlugins.size() - 1; i >= 0; i--) {PluginInfo<T> pluginInfo = mPlugins.get(i);mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED, pluginInfo.mPlugin).sendToTarget();}mPlugins.clear();handleQueryPlugins(null);break;......}}

又调用了handleQueryPlugins方法:

        private void handleQueryPlugins(String pkgName) {// This isn't actually a service and shouldn't ever be started, but is// a convenient PM based way to manage our plugins.Intent intent = new Intent(mAction);if (pkgName != null) {intent.setPackage(pkgName);}List<ResolveInfo> result = mPm.queryIntentServices(intent, 0);if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins");if (result.size() > 1 && !mAllowMultiple) {// TODO: Show warning.Log.w(TAG, "Multiple plugins found for " + mAction);if (DEBUG) {for (ResolveInfo info : result) {ComponentName name = new ComponentName(info.serviceInfo.packageName,info.serviceInfo.name);Log.w(TAG, "  " + name);}}return;}for (ResolveInfo info : result) {ComponentName name = new ComponentName(info.serviceInfo.packageName,info.serviceInfo.name);PluginInfo<T> t = handleLoadPlugin(name);if (t == null) continue;// add plugin before sending PLUGIN_CONNECTED messagemPlugins.add(t);mMainHandler.obtainMessage(mMainHandler.PLUGIN_CONNECTED, t).sendToTarget();}}

这里调用了handleLoadPlugin加载Plugin的子类:

        protected PluginInfo<T> handleLoadPlugin(ComponentName component) {// This was already checked, but do it again here to make extra extra sure, we don't// use these on production builds.if (!isDebuggable && !isPluginWhitelisted(component)) {// Never ever ever allow these on production builds, they are only for prototyping.Log.w(TAG, "Plugin cannot be loaded on production build: " + component);return null;}if (!mManager.getPluginEnabler().isEnabled(component)) {if (DEBUG) Log.d(TAG, "Plugin is not enabled, aborting load: " + component);return null;}String pkg = component.getPackageName();String cls = component.getClassName();try {ApplicationInfo info = mPm.getApplicationInfo(pkg, 0);// TODO: This probably isn't needed given that we don't have IGNORE_SECURITY onif (mPm.checkPermission(PLUGIN_PERMISSION, pkg)!= PackageManager.PERMISSION_GRANTED) {Log.d(TAG, "Plugin doesn't have permission: " + pkg);return null;}// Create our own ClassLoader so we can use our own code as the parent.ClassLoader classLoader = mManager.getClassLoader(info);Context pluginContext = new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader);Class<?> pluginClass = Class.forName(cls, true, classLoader);// TODO: Only create the plugin before version check if we need it for// legacy version check.T plugin = (T) pluginClass.newInstance();try {VersionInfo version = checkVersion(pluginClass, plugin, mVersion);if (DEBUG) Log.d(TAG, "createPlugin");return new PluginInfo(pkg, cls, plugin, pluginContext, version);} catch (InvalidVersionException e) {final int icon = Resources.getSystem().getIdentifier("stat_sys_warning", "drawable", "android");final int color = Resources.getSystem().getIdentifier("system_notification_accent_color", "color", "android");final Notification.Builder nb = new Notification.Builder(mContext,PluginManager.NOTIFICATION_CHANNEL_ID).setStyle(new Notification.BigTextStyle()).setSmallIcon(icon).setWhen(0).setShowWhen(false).setVisibility(Notification.VISIBILITY_PUBLIC).setColor(mContext.getColor(color));String label = cls;try {label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString();} catch (NameNotFoundException e2) {}if (!e.isTooNew()) {// Localization not required as this will never ever appear in a user build.nb.setContentTitle("Plugin \"" + label + "\" is too old").setContentText("Contact plugin developer to get an updated"+ " version.\n" + e.getMessage());} else {// Localization not required as this will never ever appear in a user build.nb.setContentTitle("Plugin \"" + label + "\" is too new").setContentText("Check to see if an OTA is available.\n"+ e.getMessage());}Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData(Uri.parse("package://" + component.flattenToString()));PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0);nb.addAction(new Action.Builder(null, "Disable plugin", pi).build());mContext.getSystemService(NotificationManager.class).notify(SystemMessage.NOTE_PLUGIN, nb.build());// TODO: Warn user.Log.w(TAG, "Plugin has invalid interface version " + plugin.getVersion()+ ", expected " + mVersion);return null;}} catch (Throwable e) {Log.w(TAG, "Couldn't load plugin: " + pkg, e);return null;}}

在handleQueryPlugins方法的最后还给MainHandler发送了一个PLUGIN_CONNECTED的消息,MainHandler也是PluginInstanceManager的内部类,其handleMessage方法:

        @Overridepublic void handleMessage(Message msg) {switch (msg.what) {case PLUGIN_CONNECTED:if (DEBUG) Log.d(TAG, "onPluginConnected");PluginPrefs.setHasPlugins(mContext);PluginInfo<T> info = (PluginInfo<T>) msg.obj;mManager.handleWtfs();if (!(msg.obj instanceof PluginFragment)) {// Only call onDestroy for plugins that aren't fragments, as fragments// will get the onCreate as part of the fragment lifecycle.info.mPlugin.onCreate(mContext, info.mPluginContext);}mListener.onPluginConnected(info.mPlugin, info.mPluginContext);break;case PLUGIN_DISCONNECTED:if (DEBUG) Log.d(TAG, "onPluginDisconnected");mListener.onPluginDisconnected((T) msg.obj);if (!(msg.obj instanceof PluginFragment)) {// Only call onDestroy for plugins that aren't fragments, as fragments// will get the onDestroy as part of the fragment lifecycle.((T) msg.obj).onDestroy();}break;default:super.handleMessage(msg);break;}}

在这里调用了Plugin的onCreate方法和PluginListener的onPluginConnected方法。

SystemUI的Plugin - 安卓R相关推荐

  1. 过渡动画 - 安卓R

    本文介绍安卓动画系统中的过渡动画流程. 过渡动画简介 一般过渡动画有在system_server进程中执行的本地动画和在例如systemui等进程执行的远程动画两种情况. 常见的本地动画:App内切换 ...

  2. 安卓动画系统 - 安卓R

    重要类介绍 Animation Animation定义在frameworks/base/core/java/android/view/animation/Animation.java,其子类有Tran ...

  3. 转屏动画 - 安卓R

    本文介绍安卓动画系统中的转屏动画流程. 1 旋转的开始 若当前屏幕方向需要旋转,会调用frameworks/base/services/core/java/com/android/server/wm/ ...

  4. 截屏流程 - 安卓R

    截屏类型有三种,分别是全屏截屏.区域截屏和使用提供的图片作为截屏,定义在frameworks/base/core/java/android/view/WindowManager.java中: /*** ...

  5. 【安卓R 源码】Ripple 水波纹效果源码

    安卓使用ripple实现点击时的涟漪效果 - 简书 https://www.jb51.net/article/145309.htm Android:RippleDrawable 水波纹/涟漪效果 - ...

  6. MASA MAUI Plugin 安卓蓝牙低功耗(一)蓝牙扫描

    项目背景 MAUI的出现,赋予了广大Net开发者开发多平台应用的能力,MAUI 是Xamarin.Forms演变而来,但是相比Xamarin性能更好,可扩展性更强,结构更简单.但是MAUI对于平台相关 ...

  7. 亮屏流程 - 安卓R

    PhoneWindowManager响应电源键 首先按下power键后调用frameworks/base/services/core/java/com/android/server/policy/Ph ...

  8. 灭屏流程 - 安卓R

    PhoneWindowManager响应电源键 首先按下power键后调用frameworks/base/services/core/java/com/android/server/policy/Ph ...

  9. 【安卓R 源码】获取音频焦点和释放音频焦点

    一. 获取焦点流程 1. 电话焦点只有系统可以申请,如果是电话焦点,系统会把所有多媒体和游戏的音频流实例全部mute.同理电话焦点释放会解除mute操作 2. 系统管理的焦点栈有大小限制限制为100. ...

最新文章

  1. Android Studio 常用快捷键分类整理
  2. Eclipse使用EGit管理git@OSC项目
  3. 8086的内存分段机制
  4. Ruby之旅—Ruby的Hello World
  5. win7任务栏计算机图标,Win7系统任务栏怎么添加显示桌面图标 显示桌面图标如何放到win7任务栏...
  6. Python函数式编程简介(五)偏函数
  7. 机箱一直反复开机熄火_小身材大容量,老炮九州风神魔方110机箱+DQ 650ST+玄冰400双刃装机体验...
  8. hbase java 分页查询_HBase伪快速分页查询
  9. spark sql之日期函数
  10. python爬取豆瓣电影top250并保存为xlsx_批量抓取豆瓣电影TOP250数据
  11. 信息系统项目管理师考试后多久出成绩?
  12. c语言编译defined,#if defined(__GNUC__)的意思是不是如果使用的是GCC编译器?
  13. Material Design系列之BottomSheet详解
  14. Spring IoC 容器的设计与实现原理
  15. python群发邮件
  16. C#中位枚举(Flags)
  17. linux - 中断子系统分析(1) -- GICv3硬件架构
  18. 在线博客系统——文章详情(redis incr自增实现增加阅读数和评论数)
  19. java为PDF添加水印,图片水印和文字水印
  20. word填充图片如何保持和原图比例一致_技术分析 | 为什么一张图片就能苹果手机重启...

热门文章

  1. android.os.FileUriExposedException: file:///storage/emulated/0/myxmpp/154094
  2. 黑白美女照上色系列,人工智能一键给黑白照片上色
  3. 等比数列等差数列求和
  4. 基于ssm的万卷图书馆借阅管理平台#计算机毕业设计
  5. 从零开始构建PHP版mud游戏(一)
  6. html中为什么h1比h3小,认识HTML中h1 h2 h3 h4标签
  7. NFT为实体经济赋能
  8. 中国全自动洗地机器行业现状调研及趋势分析报告
  9. Android socket 实现 wify 通信,简易聊天室 (一)
  10. AD创建自己的原理图库之设置网格