Tip for long press to enter CAB

Bug: 9859881, 9572851

Change-Id: I1603651084fb9ded479af69e17bd54cdab693c2e
diff --git a/proguard.flags b/proguard.flags
index 6bcae61..cb8d15f 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -70,3 +70,7 @@
 -keepclasseswithmembers class com.android.mail.ui.ConversationPhotoTeaserView {
   *** setAnimatedHeight(...);
 }
+
+-keepclasseswithmembers class com.android.mail.ui.ConversationLongPressTipView {
+  *** setAnimatedHeight(...);
+}
diff --git a/res/layout/conversation_long_press_to_select_tip_view.xml b/res/layout/conversation_long_press_to_select_tip_view.xml
new file mode 100644
index 0000000..448ecee
--- /dev/null
+++ b/res/layout/conversation_long_press_to_select_tip_view.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2013 Google Inc. -->
+<com.android.mail.ui.ConversationLongPressTipView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@color/swiped_bg_color" >
+
+    <LinearLayout
+        android:id="@+id/swipeable_content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/conversation_read_selector"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/text"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginBottom="12dp"
+            android:layout_marginTop="12dp"
+            android:layout_marginLeft="16dp"
+            android:layout_marginStart="16dp"
+            android:layout_weight="1"
+            android:duplicateParentState="true"
+            android:fontFamily="sans-serif-light"
+            android:text="@string/long_press_to_select_tip"
+            android:textColor="@color/teaser_main_text"
+            android:textSize="16sp" />
+
+        <View
+            android:id="@+id/dismiss_separator"
+            android:layout_width="1dip"
+            android:layout_height="match_parent"
+            android:background="@color/teaser_main_text"
+            android:layout_marginTop="16dp"
+            android:layout_marginBottom="16dp"
+            android:layout_marginLeft="16dp"
+            android:layout_marginStart="16dp" />
+
+        <ImageButton
+            android:id="@+id/dismiss_button"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:background="?android:attr/selectableItemBackground"
+            android:clickable="true"
+            android:paddingLeft="20dip"
+            android:paddingStart="20dip"
+            android:paddingRight="28dip"
+            android:paddingEnd="28dip"
+            android:scaleType="center"
+            android:contentDescription="@string/dismiss_tip_hover_text"
+            android:src="@drawable/ic_cancel_holo_light" />
+    </LinearLayout>
+
+</com.android.mail.ui.ConversationLongPressTipView>
diff --git a/res/layout/conversation_photo_teaser_view.xml b/res/layout/conversation_photo_teaser_view.xml
index 46318f9..86eac00 100644
--- a/res/layout/conversation_photo_teaser_view.xml
+++ b/res/layout/conversation_photo_teaser_view.xml
@@ -54,9 +54,12 @@
             android:background="?android:attr/selectableItemBackground"
             android:clickable="true"
             android:paddingLeft="20dip"
+            android:paddingStart="20dip"
             android:paddingRight="28dip"
+            android:paddingEnd="28dip"
             android:scaleType="center"
+            android:contentDescription="@string/dismiss_tip_hover_text"
             android:src="@drawable/ic_cancel_holo_light" />
     </LinearLayout>
 
-</com.android.mail.ui.ConversationPhotoTeaserView>
\ No newline at end of file
+</com.android.mail.ui.ConversationPhotoTeaserView>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 919eb0a..4b104e7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -941,6 +941,8 @@
 
     <string name="conversation_photo_welcome_text">Touch a sender image to select that conversation.</string>
 
+    <string name="long_press_to_select_tip">Touch &amp; hold to select one conversation, then touch to select more.</string>
+
     <!-- Content description for the folder icon for nested folders. -->
     <string name="folder_icon_desc">Folder icon</string>
 
@@ -965,4 +967,7 @@
     <!-- Shown in the message ad header and in the list item for a message ad. [CHAR LIMIT=15]-->
     <string name="ads">Ads</string>
 
+    <!-- Content description for the "X" image icon for dismissing a tip. This is used for spoken description of the icon when touch explore is enabled. [CHAR LIMIT=50] -->
+    <string name="dismiss_tip_hover_text">Dismiss tip</string>
+
 </resources>
diff --git a/src/com/android/mail/preferences/MailPrefs.java b/src/com/android/mail/preferences/MailPrefs.java
index 269c7b4..59bf8b7 100644
--- a/src/com/android/mail/preferences/MailPrefs.java
+++ b/src/com/android/mail/preferences/MailPrefs.java
@@ -82,6 +82,9 @@
 
         public static final String SHOW_SENDER_IMAGES = "conversation-list-sender-image";
 
+        public static final String
+                LONG_PRESS_TO_SELECT_TIP_SHOWN = "long-press-to-select-tip-shown";
+
         public static final ImmutableSet<String> BACKUP_KEYS =
                 new ImmutableSet.Builder<String>()
                 .add(DEFAULT_REPLY_ALL)
@@ -90,6 +93,7 @@
                 .add(DISPLAY_IMAGES)
                 .add(DISPLAY_IMAGES_PATTERNS)
                 .add(SHOW_SENDER_IMAGES)
+                .add(LONG_PRESS_TO_SELECT_TIP_SHOWN)
                 .build();
     }
 
@@ -248,7 +252,7 @@
     }
 
     /**
-     * Returns whether the teaser has bee shown before
+     * Returns whether the teaser has been shown before
      */
     public boolean isConversationPhotoTeaserAlreadyShown() {
         return getSharedPreferences()
@@ -263,10 +267,18 @@
     }
 
     /**
-     * Reset the flag so that next time, the teaser will be shown again
+     * Returns whether the tip has been shown before
      */
-    public void resetConversationPhotoTeaserAlreadyShown() {
-        getEditor().putBoolean(PreferenceKeys.CONVERSATION_PHOTO_TEASER_SHOWN, false).apply();
+    public boolean isLongPressToSelectTipAlreadyShown() {
+        // Using an int instead of boolean here in case we need to reshow the tip (don't have
+        // to use a new preference name).
+        return getSharedPreferences()
+                .getInt(PreferenceKeys.LONG_PRESS_TO_SELECT_TIP_SHOWN, 0) > 1;
+    }
+
+    public void setLongPressToSelectTipAlreadyShown() {
+        getEditor().putInt(PreferenceKeys.LONG_PRESS_TO_SELECT_TIP_SHOWN, 1).apply();
+        notifyBackupPreferenceChanged();
     }
 
     void setSenderWhitelist(Set<String> addresses) {
diff --git a/src/com/android/mail/ui/ConversationListFragment.java b/src/com/android/mail/ui/ConversationListFragment.java
index 7b6ec49..3ed4ee5 100644
--- a/src/com/android/mail/ui/ConversationListFragment.java
+++ b/src/com/android/mail/ui/ConversationListFragment.java
@@ -334,6 +334,9 @@
             manager.initLoader(ChildFolderLoads.LOADER_CHIDREN, args, mChildCallback);
         }
 
+        // TODO: These special views are always created, doesn't matter whether they will
+        // be shown or not, as we add more views this will get more expensive. Given these are
+        // tips that are only shown once to the user, we should consider creating these on demand.
         final ConversationListHelper helper = mActivity.getConversationListHelper();
         final List<ConversationSpecialItemView> specialItemViews = helper != null ?
                 ImmutableList.copyOf(helper.makeConversationListSpecialViews(
diff --git a/src/com/android/mail/ui/ConversationListHelper.java b/src/com/android/mail/ui/ConversationListHelper.java
index dec28a3..707f5f0 100644
--- a/src/com/android/mail/ui/ConversationListHelper.java
+++ b/src/com/android/mail/ui/ConversationListHelper.java
@@ -39,8 +39,14 @@
                         .inflate(R.layout.conversation_photo_teaser_view, null);
 
 
+        // Long press to select tip
+        final ConversationLongPressTipView conversationLongPressTipView =
+                (ConversationLongPressTipView) LayoutInflater.from(context)
+                        .inflate(R.layout.conversation_long_press_to_select_tip_view, null);
+
         final ArrayList<ConversationSpecialItemView> itemViews = Lists.newArrayList();
         itemViews.add(conversationPhotoTeaser);
+        itemViews.add(conversationLongPressTipView);
         return itemViews;
     }
 }
diff --git a/src/com/android/mail/ui/ConversationLongPressTipView.java b/src/com/android/mail/ui/ConversationLongPressTipView.java
new file mode 100644
index 0000000..2335f8c
--- /dev/null
+++ b/src/com/android/mail/ui/ConversationLongPressTipView.java
@@ -0,0 +1,190 @@
+package com.android.mail.ui;
+
+import com.android.mail.R;
+import com.android.mail.browse.ConversationCursor;
+import com.android.mail.preferences.MailPrefs;
+import com.android.mail.providers.Folder;
+
+import android.animation.ObjectAnimator;
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.FrameLayout;
+
+/**
+ * A tip to educate users about long press to enter CAB mode.  Appears on top of conversation list.
+ */
+// TODO: this class was shamelessly copied from ConversationPhotoTeaserView.  Look into
+// extracting a common base class.
+public class ConversationLongPressTipView extends FrameLayout
+        implements ConversationSpecialItemView, SwipeableItemView {
+
+    private static int sScrollSlop = 0;
+    private static int sShrinkAnimationDuration;
+
+    private final MailPrefs mMailPrefs;
+    private AnimatedAdapter mAdapter;
+
+    private View mSwipeableContent;
+
+    private boolean mShow;
+    private int mAnimatedHeight = -1;
+
+    public ConversationLongPressTipView(final Context context) {
+        this(context, null);
+    }
+
+    public ConversationLongPressTipView(final Context context, final AttributeSet attrs) {
+        this(context, attrs, -1);
+    }
+
+    public ConversationLongPressTipView(
+            final Context context, final AttributeSet attrs, final int defStyle) {
+        super(context, attrs, defStyle);
+
+        final Resources resources = context.getResources();
+
+        if (sScrollSlop == 0) {
+            sScrollSlop = resources.getInteger(R.integer.swipeScrollSlop);
+            sShrinkAnimationDuration = resources.getInteger(
+                    R.integer.shrink_animation_duration);
+        }
+
+        mMailPrefs = MailPrefs.get(context);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mSwipeableContent = findViewById(R.id.swipeable_content);
+
+        findViewById(R.id.dismiss_button).setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                dismiss();
+            }
+        });
+    }
+
+    @Override
+    public void onUpdate(String account, Folder folder, ConversationCursor cursor) {
+        // It's possible user has enabled/disabled sender images in settings, which affects
+        // whether we want to show this tip or not.
+        mShow = checkWhetherToShow();
+    }
+
+    @Override
+    public boolean getShouldDisplayInList() {
+        mShow = checkWhetherToShow();
+        return mShow;
+    }
+
+    private boolean checkWhetherToShow() {
+        // show if 1) sender images are disabled 2) there are items
+        return !shouldShowSenderImage() && !mAdapter.isEmpty()
+                && !mMailPrefs.isLongPressToSelectTipAlreadyShown();
+    }
+
+    @Override
+    public int getPosition() {
+        // We want this teaser to go before the first real conversation
+        // If another teaser wants position 0, we will want position 1
+        return mAdapter.getPositionOffset(0);
+    }
+
+    @Override
+    public void setAdapter(AnimatedAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    @Override
+    public void bindLoaderManager(LoaderManager loaderManager) {
+    }
+
+    @Override
+    public void cleanup() {
+    }
+
+    @Override
+    public void onConversationSelected() {
+        // DO NOTHING
+    }
+
+    @Override
+    public void onCabModeEntered() {
+        dismiss();
+    }
+
+
+    @Override
+    public boolean acceptsUserTaps() {
+        // No, we don't allow user taps.
+        return false;
+    }
+
+    @Override
+    public void dismiss() {
+        setDismissed();
+        startDestroyAnimation();
+    }
+
+    private void setDismissed() {
+        if (mShow) {
+            mMailPrefs.setLongPressToSelectTipAlreadyShown();
+            mShow = false;
+        }
+    }
+
+    protected boolean shouldShowSenderImage() {
+        return mMailPrefs.getShowSenderImages();
+    }
+
+    @Override
+    public SwipeableView getSwipeableView() {
+        return SwipeableView.from(mSwipeableContent);
+    }
+
+    @Override
+    public boolean canChildBeDismissed() {
+        return true;
+    }
+
+    @Override
+    public float getMinAllowScrollDistance() {
+        return sScrollSlop;
+    }
+
+    private void startDestroyAnimation() {
+        final int start = getHeight();
+        final int end = 0;
+        mAnimatedHeight = start;
+        final ObjectAnimator heightAnimator =
+                ObjectAnimator.ofInt(this, "animatedHeight", start, end);
+        heightAnimator.setInterpolator(new DecelerateInterpolator(2.0f));
+        heightAnimator.setDuration(sShrinkAnimationDuration);
+        heightAnimator.start();
+    }
+
+    /**
+     * This method is used by the animator.  It is explicitly kept in proguard.flags to prevent it
+     * from being removed, inlined, or obfuscated.
+     * Edit ./packages/apps/UnifiedEmail/proguard.flags
+     * In the future, we want to use @Keep
+     */
+    public void setAnimatedHeight(final int height) {
+        mAnimatedHeight = height;
+        requestLayout();
+    }
+
+    @Override
+    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+        if (mAnimatedHeight == -1) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        } else {
+            setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mAnimatedHeight);
+        }
+    }
+
+}