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