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