NotificationMenuRow.java revision 0b8a985cd6ad499e8532d8468ea0ef68147eed2d
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
17package com.android.systemui.statusbar;
18
19import static com.android.systemui.SwipeHelper.SWIPED_FAR_ENOUGH_SIZE_FRACTION;
20
21import java.util.ArrayList;
22
23import com.android.systemui.Interpolators;
24import com.android.systemui.R;
25import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
26import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
27import com.android.systemui.statusbar.NotificationGuts.GutsContent;
28import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
29
30import android.animation.Animator;
31import android.animation.AnimatorListenerAdapter;
32import android.animation.ValueAnimator;
33import android.app.Notification;
34import android.content.Context;
35import android.content.res.Resources;
36import android.graphics.drawable.Drawable;
37import android.os.Handler;
38import android.os.Looper;
39import android.util.Log;
40import android.service.notification.StatusBarNotification;
41import android.view.LayoutInflater;
42import android.view.MotionEvent;
43import android.view.View;
44import android.view.ViewGroup;
45import android.widget.FrameLayout;
46import android.widget.FrameLayout.LayoutParams;
47
48public 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