1 package com.android.mail.ui; 2 3 import android.animation.ObjectAnimator; 4 import android.app.LoaderManager; 5 import android.content.Context; 6 import android.content.res.Resources; 7 import android.os.Bundle; 8 import android.util.AttributeSet; 9 import android.view.View; 10 import android.view.animation.DecelerateInterpolator; 11 import android.widget.FrameLayout; 12 import android.widget.ImageView; 13 import android.widget.LinearLayout; 14 import android.widget.TextView; 15 16 import com.android.mail.R; 17 import com.android.mail.analytics.Analytics; 18 import com.android.mail.browse.ConversationCursor; 19 import com.android.mail.preferences.MailPrefs; 20 import com.android.mail.providers.Folder; 21 import com.android.mail.utils.Utils; 22 23 /** 24 * A teaser to introduce people to the contact photo check boxes 25 */ 26 public class ConversationPhotoTeaserView extends FrameLayout 27 implements ConversationSpecialItemView, SwipeableItemView { 28 private static int sScrollSlop = 0; 29 private static int sShrinkAnimationDuration; 30 31 private final MailPrefs mMailPrefs; 32 private AnimatedAdapter mAdapter; 33 34 private View mSwipeableContent; 35 36 private boolean mShown; 37 private int mAnimatedHeight = -1; 38 private boolean mNeedLayout; 39 private int mTextTop; 40 41 private View mTeaserRightEdge; 42 /** Whether we are on a tablet device or not */ 43 private final boolean mTabletDevice; 44 /** When in conversation mode, true if the list is hidden */ 45 private final boolean mListCollapsible; 46 47 public ConversationPhotoTeaserView(final Context context) { 48 this(context, null); 49 } 50 51 public ConversationPhotoTeaserView(final Context context, final AttributeSet attrs) { 52 this(context, attrs, -1); 53 } 54 55 public ConversationPhotoTeaserView( 56 final Context context, final AttributeSet attrs, final int defStyle) { 57 super(context, attrs, defStyle); 58 59 final Resources resources = context.getResources(); 60 61 synchronized (ConversationPhotoTeaserView.class) { 62 if (sScrollSlop == 0) { 63 sScrollSlop = resources.getInteger(R.integer.swipeScrollSlop); 64 sShrinkAnimationDuration = resources.getInteger( 65 R.integer.shrink_animation_duration); 66 } 67 } 68 69 mMailPrefs = MailPrefs.get(context); 70 71 mNeedLayout = true; 72 73 mTabletDevice = Utils.useTabletUI(resources); 74 mListCollapsible = resources.getBoolean(R.bool.list_collapsible); 75 } 76 77 @Override 78 protected void onFinishInflate() { 79 mSwipeableContent = findViewById(R.id.swipeable_content); 80 81 findViewById(R.id.dismiss_button).setOnClickListener(new OnClickListener() { 82 @Override 83 public void onClick(View v) { 84 dismiss(); 85 } 86 }); 87 88 mTeaserRightEdge = findViewById(R.id.teaser_right_edge); 89 } 90 91 @Override 92 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 93 super.onLayout(changed, left, top, right, bottom); 94 95 final TextView text = (TextView) findViewById(R.id.text); 96 final ImageView arrow = (ImageView) findViewById(R.id.arrow); 97 98 // We post to avoid calling layout within layout 99 arrow.post(new Runnable() { 100 @Override 101 public void run() { 102 103 // The text top is changed when we move the arrow, so we need to 104 // do multiple passes 105 int textTop = text.getTop(); 106 if (mNeedLayout || textTop != mTextTop) { 107 mNeedLayout = false; 108 mTextTop = textTop; 109 110 final int lineHeight = text.getLineHeight(); 111 final LinearLayout.LayoutParams arrowParams = (LinearLayout.LayoutParams) arrow 112 .getLayoutParams(); 113 arrowParams.topMargin = mTextTop + lineHeight / 2; 114 arrow.setLayoutParams(arrowParams); 115 } 116 arrow.setVisibility(View.VISIBLE); 117 } 118 }); 119 } 120 121 @Override 122 public void onUpdate(Folder folder, ConversationCursor cursor) { 123 // Do nothing 124 } 125 126 @Override 127 public void onGetView() { 128 // Do nothing 129 } 130 131 @Override 132 public boolean getShouldDisplayInList() { 133 // show if 1) sender images are enabled 2) there are items 134 mShown = shouldShowSenderImage() && !mAdapter.isEmpty() 135 && !mMailPrefs.isConversationPhotoTeaserAlreadyShown(); 136 return mShown; 137 } 138 139 @Override 140 public int getPosition() { 141 return 0; 142 } 143 144 @Override 145 public void setAdapter(AnimatedAdapter adapter) { 146 mAdapter = adapter; 147 } 148 149 @Override 150 public void bindFragment(final LoaderManager loaderManager, final Bundle savedInstanceState) { 151 } 152 153 @Override 154 public void cleanup() { 155 } 156 157 @Override 158 public void onConversationSelected() { 159 // DO NOTHING 160 } 161 162 @Override 163 public void onCabModeEntered() { 164 if (mShown) { 165 dismiss(); 166 } 167 } 168 169 @Override 170 public void onCabModeExited() { 171 // Do nothing 172 } 173 174 @Override 175 public void onConversationListVisibilityChanged(final boolean visible) { 176 // Do nothing 177 } 178 179 @Override 180 public void saveInstanceState(final Bundle outState) { 181 // Do nothing 182 } 183 184 @Override 185 public boolean acceptsUserTaps() { 186 // No, we don't allow user taps. 187 return false; 188 } 189 190 @Override 191 public void dismiss() { 192 setDismissed(); 193 startDestroyAnimation(); 194 } 195 196 private void setDismissed() { 197 if (mShown) { 198 mMailPrefs.setConversationPhotoTeaserAlreadyShown(); 199 mShown = false; 200 Analytics.getInstance().sendEvent("list_swipe", "photo_teaser", null, 0); 201 } 202 } 203 204 protected boolean shouldShowSenderImage() { 205 return mMailPrefs.getShowSenderImages(); 206 } 207 208 @Override 209 public SwipeableView getSwipeableView() { 210 return SwipeableView.from(mSwipeableContent); 211 } 212 213 @Override 214 public boolean canChildBeDismissed() { 215 return true; 216 } 217 218 @Override 219 public float getMinAllowScrollDistance() { 220 return sScrollSlop; 221 } 222 223 private void startDestroyAnimation() { 224 final int start = getHeight(); 225 final int end = 0; 226 mAnimatedHeight = start; 227 final ObjectAnimator heightAnimator = 228 ObjectAnimator.ofInt(this, "animatedHeight", start, end); 229 heightAnimator.setInterpolator(new DecelerateInterpolator(2.0f)); 230 heightAnimator.setDuration(sShrinkAnimationDuration); 231 heightAnimator.start(); 232 233 /* 234 * Ideally, we would like to call mAdapter.notifyDataSetChanged() in a listener's 235 * onAnimationEnd(), but we are in the middle of a touch event, and this will cause all the 236 * views to get recycled, which will cause problems. 237 * 238 * Instead, we'll just leave the item in the list with a height of 0, and the next 239 * notifyDatasetChanged() will remove it from the adapter. 240 */ 241 } 242 243 /** 244 * This method is used by the animator. It is explicitly kept in proguard.flags to prevent it 245 * from being removed, inlined, or obfuscated. 246 * Edit ./packages/apps/UnifiedEmail/proguard.flags 247 * In the future, we want to use @Keep 248 */ 249 public void setAnimatedHeight(final int height) { 250 mAnimatedHeight = height; 251 requestLayout(); 252 } 253 254 @Override 255 protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { 256 if (Utils.getDisplayListRightEdgeEffect(mTabletDevice, mListCollapsible, 257 mAdapter.getViewMode())) { 258 mTeaserRightEdge.setVisibility(VISIBLE); 259 } else { 260 mTeaserRightEdge.setVisibility(GONE); 261 } 262 263 if (mAnimatedHeight == -1) { 264 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 265 } else { 266 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mAnimatedHeight); 267 } 268 } 269 } 270