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