Home | History | Annotate | Download | only in ui
      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