Home | History | Annotate | Download | only in statusbar
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.systemui.statusbar;
     18 
     19 import static com.android.systemui.SwipeHelper.SWIPED_FAR_ENOUGH_SIZE_FRACTION;
     20 
     21 import java.util.ArrayList;
     22 
     23 import com.android.systemui.Interpolators;
     24 import com.android.systemui.R;
     25 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
     26 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
     27 import com.android.systemui.statusbar.NotificationGuts.GutsContent;
     28 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
     29 
     30 import android.animation.Animator;
     31 import android.animation.AnimatorListenerAdapter;
     32 import android.animation.ValueAnimator;
     33 import android.app.Notification;
     34 import android.content.Context;
     35 import android.content.res.Resources;
     36 import android.graphics.drawable.Drawable;
     37 import android.os.Handler;
     38 import android.os.Looper;
     39 import android.util.Log;
     40 import android.service.notification.StatusBarNotification;
     41 import android.view.LayoutInflater;
     42 import android.view.MotionEvent;
     43 import android.view.View;
     44 import android.view.ViewGroup;
     45 import android.widget.FrameLayout;
     46 import android.widget.FrameLayout.LayoutParams;
     47 
     48 public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnClickListener,
     49         ExpandableNotificationRow.LayoutListener {
     50 
     51     private static final boolean DEBUG = false;
     52     private static final String TAG = "swipe";
     53 
     54     private static final int ICON_ALPHA_ANIM_DURATION = 200;
     55     private static final long SHOW_MENU_DELAY = 60;
     56     private static final long SWIPE_MENU_TIMING = 200;
     57 
     58     // Notification must be swiped at least this fraction of a single menu item to show menu
     59     private static final float SWIPED_FAR_ENOUGH_MENU_FRACTION = 0.25f;
     60     private static final float SWIPED_FAR_ENOUGH_MENU_UNCLEARABLE_FRACTION = 0.15f;
     61 
     62     // When the menu is displayed, the notification must be swiped within this fraction of a single
     63     // menu item to snap back to menu (else it will cover the menu or it'll be dismissed)
     64     private static final float SWIPED_BACK_ENOUGH_TO_COVER_FRACTION = 0.2f;
     65 
     66     private ExpandableNotificationRow mParent;
     67 
     68     private Context mContext;
     69     private FrameLayout mMenuContainer;
     70     private MenuItem mInfoItem;
     71     private ArrayList<MenuItem> mMenuItems;
     72     private OnMenuEventListener mMenuListener;
     73 
     74     private ValueAnimator mFadeAnimator;
     75     private boolean mAnimating;
     76     private boolean mMenuFadedIn;
     77 
     78     private boolean mOnLeft;
     79     private boolean mIconsPlaced;
     80 
     81     private boolean mDismissing;
     82     private boolean mSnapping;
     83     private float mTranslation;
     84 
     85     private int[] mIconLocation = new int[2];
     86     private int[] mParentLocation = new int[2];
     87 
     88     private float mHorizSpaceForIcon = -1;
     89     private int mVertSpaceForIcons = -1;
     90     private int mIconPadding = -1;
     91 
     92     private float mAlpha = 0f;
     93     private float mPrevX;
     94 
     95     private CheckForDrag mCheckForDrag;
     96     private Handler mHandler;
     97 
     98     private boolean mMenuSnappedTo;
     99     private boolean mMenuSnappedOnLeft;
    100     private boolean mShouldShowMenu;
    101 
    102     private NotificationSwipeActionHelper mSwipeHelper;
    103     private boolean mIsUserTouching;
    104 
    105     public NotificationMenuRow(Context context) {
    106         mContext = context;
    107         mShouldShowMenu = context.getResources().getBoolean(R.bool.config_showNotificationGear);
    108         mHandler = new Handler(Looper.getMainLooper());
    109         mMenuItems = new ArrayList<>();
    110     }
    111 
    112     @Override
    113     public ArrayList<MenuItem> getMenuItems(Context context) {
    114         return mMenuItems;
    115     }
    116 
    117     @Override
    118     public MenuItem getLongpressMenuItem(Context context) {
    119         return mInfoItem;
    120     }
    121 
    122     @Override
    123     public void setSwipeActionHelper(NotificationSwipeActionHelper helper) {
    124         mSwipeHelper = helper;
    125     }
    126 
    127     @Override
    128     public void setMenuClickListener(OnMenuEventListener listener) {
    129         mMenuListener = listener;
    130     }
    131 
    132     @Override
    133     public void createMenu(ViewGroup parent, StatusBarNotification sbn) {
    134         mParent = (ExpandableNotificationRow) parent;
    135         createMenuViews(true /* resetState */);
    136     }
    137 
    138     @Override
    139     public boolean isMenuVisible() {
    140         return mAlpha > 0;
    141     }
    142 
    143     @Override
    144     public View getMenuView() {
    145         return mMenuContainer;
    146     }
    147 
    148     @Override
    149     public void resetMenu() {
    150         resetState(true);
    151     }
    152 
    153     @Override
    154     public void onNotificationUpdated(StatusBarNotification sbn) {
    155         if (mMenuContainer == null) {
    156             // Menu hasn't been created yet, no need to do anything.
    157             return;
    158         }
    159         createMenuViews(!isMenuVisible() /* resetState */);
    160     }
    161 
    162     @Override
    163     public void onConfigurationChanged() {
    164         mParent.setLayoutListener(this);
    165     }
    166 
    167     @Override
    168     public void onLayout() {
    169         mIconsPlaced = false; // Force icons to be re-placed
    170         setMenuLocation();
    171         mParent.removeListener();
    172     }
    173 
    174     private void createMenuViews(boolean resetState) {
    175         final Resources res = mContext.getResources();
    176         mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
    177         mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
    178         mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
    179         mMenuItems.clear();
    180         // Construct the menu items based on the notification
    181         if (mParent != null && mParent.getStatusBarNotification() != null) {
    182             int flags = mParent.getStatusBarNotification().getNotification().flags;
    183             boolean isForeground = (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
    184             if (!isForeground) {
    185                 // Only show snooze for non-foreground notifications
    186                 mMenuItems.add(createSnoozeItem(mContext));
    187             }
    188         }
    189         mInfoItem = createInfoItem(mContext);
    190         mMenuItems.add(mInfoItem);
    191 
    192         // Construct the menu views
    193         if (mMenuContainer != null) {
    194             mMenuContainer.removeAllViews();
    195         } else {
    196             mMenuContainer = new FrameLayout(mContext);
    197         }
    198         for (int i = 0; i < mMenuItems.size(); i++) {
    199             addMenuView(mMenuItems.get(i), mMenuContainer);
    200         }
    201         if (resetState) {
    202             resetState(false /* notify */);
    203         } else {
    204             mIconsPlaced = false;
    205             setMenuLocation();
    206             if (!mIsUserTouching) {
    207                 // If the # of items showing changed we need to update the snap position
    208                 showMenu(mParent, mOnLeft ? getSpaceForMenu() : -getSpaceForMenu(),
    209                         0 /* velocity */);
    210             }
    211         }
    212     }
    213 
    214     private void resetState(boolean notify) {
    215         setMenuAlpha(0f);
    216         mIconsPlaced = false;
    217         mMenuFadedIn = false;
    218         mAnimating = false;
    219         mSnapping = false;
    220         mDismissing = false;
    221         mMenuSnappedTo = false;
    222         setMenuLocation();
    223         if (mMenuListener != null && notify) {
    224             mMenuListener.onMenuReset(mParent);
    225         }
    226     }
    227 
    228     @Override
    229     public boolean onTouchEvent(View view, MotionEvent ev, float velocity) {
    230         final int action = ev.getActionMasked();
    231         switch (action) {
    232             case MotionEvent.ACTION_DOWN:
    233                 mSnapping = false;
    234                 if (mFadeAnimator != null) {
    235                     mFadeAnimator.cancel();
    236                 }
    237                 mHandler.removeCallbacks(mCheckForDrag);
    238                 mCheckForDrag = null;
    239                 mPrevX = ev.getRawX();
    240                 mIsUserTouching = true;
    241                 break;
    242 
    243             case MotionEvent.ACTION_MOVE:
    244                 mSnapping = false;
    245                 float diffX = ev.getRawX() - mPrevX;
    246                 mPrevX = ev.getRawX();
    247                 if (!isTowardsMenu(diffX) && isMenuLocationChange()) {
    248                     // Don't consider it "snapped" if location has changed.
    249                     mMenuSnappedTo = false;
    250 
    251                     // Changed directions, make sure we check to fade in icon again.
    252                     if (!mHandler.hasCallbacks(mCheckForDrag)) {
    253                         // No check scheduled, set null to schedule a new one.
    254                         mCheckForDrag = null;
    255                     } else {
    256                         // Check scheduled, reset alpha and update location; check will fade it in
    257                         setMenuAlpha(0f);
    258                         setMenuLocation();
    259                     }
    260                 }
    261                 if (mShouldShowMenu
    262                         && !NotificationStackScrollLayout.isPinnedHeadsUp(view)
    263                         && !mParent.areGutsExposed()
    264                         && !mParent.isDark()
    265                         && (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag))) {
    266                     // Only show the menu if we're not a heads up view and guts aren't exposed.
    267                     mCheckForDrag = new CheckForDrag();
    268                     mHandler.postDelayed(mCheckForDrag, SHOW_MENU_DELAY);
    269                 }
    270                 break;
    271 
    272             case MotionEvent.ACTION_UP:
    273                 mIsUserTouching = false;
    274                 return handleUpEvent(ev, view, velocity);
    275             case MotionEvent.ACTION_CANCEL:
    276                 mIsUserTouching = false;
    277                 cancelDrag();
    278                 return false;
    279         }
    280         return false;
    281     }
    282 
    283     private boolean handleUpEvent(MotionEvent ev, View animView, float velocity) {
    284         // If the menu should not be shown, then there is no need to check if the a swipe
    285         // should result in a snapping to the menu. As a result, just check if the swipe
    286         // was enough to dismiss the notification.
    287         if (!mShouldShowMenu) {
    288             if (mSwipeHelper.isDismissGesture(ev)) {
    289                 dismiss(animView, velocity);
    290             } else {
    291                 snapBack(animView, velocity);
    292             }
    293             return true;
    294         }
    295 
    296         final boolean gestureTowardsMenu = isTowardsMenu(velocity);
    297         final boolean gestureFastEnough =
    298                 mSwipeHelper.getMinDismissVelocity() <= Math.abs(velocity);
    299         final boolean gestureFarEnough =
    300                 mSwipeHelper.swipedFarEnough(mTranslation, mParent.getWidth());
    301         final double timeForGesture = ev.getEventTime() - ev.getDownTime();
    302         final boolean showMenuForSlowOnGoing = !mParent.canViewBeDismissed()
    303                 && timeForGesture >= SWIPE_MENU_TIMING;
    304         final float menuSnapTarget = mOnLeft ? getSpaceForMenu() : -getSpaceForMenu();
    305 
    306         if (DEBUG) {
    307             Log.d(TAG, "mTranslation= " + mTranslation
    308                     + " mAlpha= " + mAlpha
    309                     + " velocity= " + velocity
    310                     + " mMenuSnappedTo= " + mMenuSnappedTo
    311                     + " mMenuSnappedOnLeft= " + mMenuSnappedOnLeft
    312                     + " mOnLeft= " + mOnLeft
    313                     + " minDismissVel= " + mSwipeHelper.getMinDismissVelocity()
    314                     + " isDismissGesture= " + mSwipeHelper.isDismissGesture(ev)
    315                     + " gestureTowardsMenu= " + gestureTowardsMenu
    316                     + " gestureFastEnough= " + gestureFastEnough
    317                     + " gestureFarEnough= " + gestureFarEnough);
    318         }
    319 
    320         if (mMenuSnappedTo && isMenuVisible() && mMenuSnappedOnLeft == mOnLeft) {
    321             // Menu was snapped to previously and we're on the same side, figure out if
    322             // we should stick to the menu, snap back into place, or dismiss
    323             final float maximumSwipeDistance = mHorizSpaceForIcon
    324                     * SWIPED_BACK_ENOUGH_TO_COVER_FRACTION;
    325             final float targetLeft = getSpaceForMenu() - maximumSwipeDistance;
    326             final float targetRight = mParent.getWidth() * SWIPED_FAR_ENOUGH_SIZE_FRACTION;
    327             boolean withinSnapMenuThreshold = mOnLeft
    328                     ? mTranslation > targetLeft && mTranslation < targetRight
    329                     : mTranslation < -targetLeft && mTranslation > -targetRight;
    330             boolean shouldSnapTo = mOnLeft ? mTranslation < targetLeft : mTranslation > -targetLeft;
    331             if (DEBUG) {
    332                 Log.d(TAG, "   withinSnapMenuThreshold= " + withinSnapMenuThreshold
    333                         + "   shouldSnapTo= " + shouldSnapTo
    334                         + "   targetLeft= " + targetLeft
    335                         + "   targetRight= " + targetRight);
    336             }
    337             if (withinSnapMenuThreshold && !mSwipeHelper.isDismissGesture(ev)) {
    338                 // Haven't moved enough to unsnap from the menu
    339                 showMenu(animView, menuSnapTarget, velocity);
    340             } else if (mSwipeHelper.isDismissGesture(ev) && !shouldSnapTo) {
    341                 // Only dismiss if we're not moving towards the menu
    342                 dismiss(animView, velocity);
    343             } else {
    344                 snapBack(animView, velocity);
    345             }
    346         } else if (!mSwipeHelper.isFalseGesture(ev)
    347                 && (swipedEnoughToShowMenu() && (!gestureFastEnough || showMenuForSlowOnGoing))
    348                 || (gestureTowardsMenu && !mSwipeHelper.isDismissGesture(ev))) {
    349             // Menu has not been snapped to previously and this is menu revealing gesture
    350             showMenu(animView, menuSnapTarget, velocity);
    351         } else if (mSwipeHelper.isDismissGesture(ev) && !gestureTowardsMenu) {
    352             dismiss(animView, velocity);
    353         } else {
    354             snapBack(animView, velocity);
    355         }
    356         return true;
    357     }
    358 
    359     private void showMenu(View animView, float targetLeft, float velocity) {
    360         mMenuSnappedTo = true;
    361         mMenuSnappedOnLeft = mOnLeft;
    362         mMenuListener.onMenuShown(animView);
    363         mSwipeHelper.snap(animView, targetLeft, velocity);
    364     }
    365 
    366     private void snapBack(View animView, float velocity) {
    367         cancelDrag();
    368         mMenuSnappedTo = false;
    369         mSnapping = true;
    370         mSwipeHelper.snap(animView, 0 /* leftTarget */, velocity);
    371     }
    372 
    373     private void dismiss(View animView, float velocity) {
    374         cancelDrag();
    375         mMenuSnappedTo = false;
    376         mDismissing = true;
    377         mSwipeHelper.dismiss(animView, velocity);
    378     }
    379 
    380     private void cancelDrag() {
    381         if (mFadeAnimator != null) {
    382             mFadeAnimator.cancel();
    383         }
    384         mHandler.removeCallbacks(mCheckForDrag);
    385     }
    386 
    387     /**
    388      * @return whether the notification has been translated enough to show the menu and not enough
    389      *         to be dismissed.
    390      */
    391     private boolean swipedEnoughToShowMenu() {
    392         final float multiplier = mParent.canViewBeDismissed()
    393                 ? SWIPED_FAR_ENOUGH_MENU_FRACTION
    394                 : SWIPED_FAR_ENOUGH_MENU_UNCLEARABLE_FRACTION;
    395         final float minimumSwipeDistance = mHorizSpaceForIcon * multiplier;
    396         return !mSwipeHelper.swipedFarEnough(0, 0) && isMenuVisible()
    397                 && (mOnLeft ? mTranslation > minimumSwipeDistance
    398                         : mTranslation < -minimumSwipeDistance);
    399     }
    400 
    401     /**
    402      * Returns whether the gesture is towards the menu location or not.
    403      */
    404     private boolean isTowardsMenu(float movement) {
    405         return isMenuVisible()
    406                 && ((mOnLeft && movement <= 0)
    407                         || (!mOnLeft && movement >= 0));
    408     }
    409 
    410     @Override
    411     public void setAppName(String appName) {
    412         if (appName == null) {
    413             return;
    414         }
    415         Resources res = mContext.getResources();
    416         final int count = mMenuItems.size();
    417         for (int i = 0; i < count; i++) {
    418             MenuItem item = mMenuItems.get(i);
    419             String description = String.format(
    420                     res.getString(R.string.notification_menu_accessibility),
    421                     appName, item.getContentDescription());
    422             View menuView = item.getMenuView();
    423             if (menuView != null) {
    424                 menuView.setContentDescription(description);
    425             }
    426         }
    427     }
    428 
    429     @Override
    430     public void onHeightUpdate() {
    431         if (mParent == null || mMenuItems.size() == 0 || mMenuContainer == null) {
    432             return;
    433         }
    434         int parentHeight = mParent.getActualHeight();
    435         float translationY;
    436         if (parentHeight < mVertSpaceForIcons) {
    437             translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2);
    438         } else {
    439             translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2;
    440         }
    441         mMenuContainer.setTranslationY(translationY);
    442     }
    443 
    444     @Override
    445     public void onTranslationUpdate(float translation) {
    446         mTranslation = translation;
    447         if (mAnimating || !mMenuFadedIn) {
    448             // Don't adjust when animating, or if the menu hasn't been shown yet.
    449             return;
    450         }
    451         final float fadeThreshold = mParent.getWidth() * 0.3f;
    452         final float absTrans = Math.abs(translation);
    453         float desiredAlpha = 0;
    454         if (absTrans == 0) {
    455             desiredAlpha = 0;
    456         } else if (absTrans <= fadeThreshold) {
    457             desiredAlpha = 1;
    458         } else {
    459             desiredAlpha = 1 - ((absTrans - fadeThreshold) / (mParent.getWidth() - fadeThreshold));
    460         }
    461         setMenuAlpha(desiredAlpha);
    462     }
    463 
    464     @Override
    465     public void onClick(View v) {
    466         if (mMenuListener == null) {
    467             // Nothing to do
    468             return;
    469         }
    470         v.getLocationOnScreen(mIconLocation);
    471         mParent.getLocationOnScreen(mParentLocation);
    472         final int centerX = (int) (mHorizSpaceForIcon / 2);
    473         final int centerY = v.getHeight() / 2;
    474         final int x = mIconLocation[0] - mParentLocation[0] + centerX;
    475         final int y = mIconLocation[1] - mParentLocation[1] + centerY;
    476         final int index = mMenuContainer.indexOfChild(v);
    477         mMenuListener.onMenuClicked(mParent, x, y, mMenuItems.get(index));
    478     }
    479 
    480     private boolean isMenuLocationChange() {
    481         boolean onLeft = mTranslation > mIconPadding;
    482         boolean onRight = mTranslation < -mIconPadding;
    483         if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
    484             return true;
    485         }
    486         return false;
    487     }
    488 
    489     private void setMenuLocation() {
    490         boolean showOnLeft = mTranslation > 0;
    491         if ((mIconsPlaced && showOnLeft == mOnLeft) || mSnapping || mMenuContainer == null
    492                 || !mMenuContainer.isAttachedToWindow()) {
    493             // Do nothing
    494             return;
    495         }
    496         final int count = mMenuContainer.getChildCount();
    497         for (int i = 0; i < count; i++) {
    498             final View v = mMenuContainer.getChildAt(i);
    499             final float left = i * mHorizSpaceForIcon;
    500             final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1));
    501             v.setX(showOnLeft ? left : right);
    502         }
    503         mOnLeft = showOnLeft;
    504         mIconsPlaced = true;
    505     }
    506 
    507     private void setMenuAlpha(float alpha) {
    508         mAlpha = alpha;
    509         if (mMenuContainer == null) {
    510             return;
    511         }
    512         if (alpha == 0) {
    513             mMenuFadedIn = false; // Can fade in again once it's gone.
    514             mMenuContainer.setVisibility(View.INVISIBLE);
    515         } else {
    516             mMenuContainer.setVisibility(View.VISIBLE);
    517         }
    518         final int count = mMenuContainer.getChildCount();
    519         for (int i = 0; i < count; i++) {
    520             mMenuContainer.getChildAt(i).setAlpha(mAlpha);
    521         }
    522     }
    523 
    524     /**
    525      * Returns the horizontal space in pixels required to display the menu.
    526      */
    527     private float getSpaceForMenu() {
    528         return mHorizSpaceForIcon * mMenuContainer.getChildCount();
    529     }
    530 
    531     private final class CheckForDrag implements Runnable {
    532         @Override
    533         public void run() {
    534             final float absTransX = Math.abs(mTranslation);
    535             final float bounceBackToMenuWidth = getSpaceForMenu();
    536             final float notiThreshold = mParent.getWidth() * 0.4f;
    537             if ((!isMenuVisible() || isMenuLocationChange())
    538                     && absTransX >= bounceBackToMenuWidth * 0.4
    539                     && absTransX < notiThreshold) {
    540                 fadeInMenu(notiThreshold);
    541             }
    542         }
    543     }
    544 
    545     private void fadeInMenu(final float notiThreshold) {
    546         if (mDismissing || mAnimating) {
    547             return;
    548         }
    549         if (isMenuLocationChange()) {
    550             setMenuAlpha(0f);
    551         }
    552         final float transX = mTranslation;
    553         final boolean fromLeft = mTranslation > 0;
    554         setMenuLocation();
    555         mFadeAnimator = ValueAnimator.ofFloat(mAlpha, 1);
    556         mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    557             @Override
    558             public void onAnimationUpdate(ValueAnimator animation) {
    559                 final float absTrans = Math.abs(transX);
    560 
    561                 boolean pastMenu = (fromLeft && transX <= notiThreshold)
    562                         || (!fromLeft && absTrans <= notiThreshold);
    563                 if (pastMenu && !mMenuFadedIn) {
    564                     setMenuAlpha((float) animation.getAnimatedValue());
    565                 }
    566             }
    567         });
    568         mFadeAnimator.addListener(new AnimatorListenerAdapter() {
    569             @Override
    570             public void onAnimationStart(Animator animation) {
    571                 mAnimating = true;
    572             }
    573 
    574             @Override
    575             public void onAnimationCancel(Animator animation) {
    576                 // TODO should animate back to 0f from current alpha
    577                 setMenuAlpha(0f);
    578             }
    579 
    580             @Override
    581             public void onAnimationEnd(Animator animation) {
    582                 mAnimating = false;
    583                 mMenuFadedIn = mAlpha == 1;
    584             }
    585         });
    586         mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
    587         mFadeAnimator.setDuration(ICON_ALPHA_ANIM_DURATION);
    588         mFadeAnimator.start();
    589     }
    590 
    591     @Override
    592     public void setMenuItems(ArrayList<MenuItem> items) {
    593         // Do nothing we use our own for now.
    594         // TODO -- handle / allow custom menu items!
    595     }
    596 
    597     public static MenuItem createSnoozeItem(Context context) {
    598         Resources res = context.getResources();
    599         NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(context)
    600                 .inflate(R.layout.notification_snooze, null, false);
    601         String snoozeDescription = res.getString(R.string.notification_menu_snooze_description);
    602         MenuItem snooze = new NotificationMenuItem(context, snoozeDescription, content,
    603                 R.drawable.ic_snooze);
    604         return snooze;
    605     }
    606 
    607     public static MenuItem createInfoItem(Context context) {
    608         Resources res = context.getResources();
    609         String infoDescription = res.getString(R.string.notification_menu_gear_description);
    610         NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate(
    611                 R.layout.notification_info, null, false);
    612         MenuItem info = new NotificationMenuItem(context, infoDescription, infoContent,
    613                 R.drawable.ic_settings);
    614         return info;
    615     }
    616 
    617     private void addMenuView(MenuItem item, ViewGroup parent) {
    618         View menuView = item.getMenuView();
    619         if (menuView != null) {
    620             parent.addView(menuView);
    621             menuView.setOnClickListener(this);
    622             FrameLayout.LayoutParams lp = (LayoutParams) menuView.getLayoutParams();
    623             lp.width = (int) mHorizSpaceForIcon;
    624             lp.height = (int) mHorizSpaceForIcon;
    625             menuView.setLayoutParams(lp);
    626         }
    627     }
    628 
    629     public static class NotificationMenuItem implements MenuItem {
    630         View mMenuView;
    631         GutsContent mGutsContent;
    632         String mContentDescription;
    633 
    634         public NotificationMenuItem(Context context, String s, GutsContent content, int iconResId) {
    635             Resources res = context.getResources();
    636             int padding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
    637             int tint = res.getColor(R.color.notification_gear_color);
    638             AlphaOptimizedImageView iv = new AlphaOptimizedImageView(context);
    639             iv.setPadding(padding, padding, padding, padding);
    640             Drawable icon = context.getResources().getDrawable(iconResId);
    641             iv.setImageDrawable(icon);
    642             iv.setColorFilter(tint);
    643             iv.setAlpha(1f);
    644             mMenuView = iv;
    645             mContentDescription = s;
    646             mGutsContent = content;
    647         }
    648 
    649         @Override
    650         public View getMenuView() {
    651             return mMenuView;
    652         }
    653 
    654         @Override
    655         public View getGutsView() {
    656             return mGutsContent.getContentView();
    657         }
    658 
    659         @Override
    660         public String getContentDescription() {
    661             return mContentDescription;
    662         }
    663     }
    664 }
    665