Home | History | Annotate | Download | only in list
      1 /*
      2 
      3  * Copyright (C) 2011 The Android Open Source Project
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 package com.android.dialer.list;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.AnimatorSet;
     22 import android.animation.ObjectAnimator;
     23 import android.content.ClipData;
     24 import android.content.Context;
     25 import android.text.TextUtils;
     26 import android.util.AttributeSet;
     27 import android.view.GestureDetector;
     28 import android.view.View;
     29 
     30 import com.android.contacts.common.ContactPhotoManager;
     31 import com.android.contacts.common.MoreContactUtils;
     32 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
     33 import com.android.contacts.common.list.ContactEntry;
     34 import com.android.contacts.common.list.ContactTileView;
     35 import com.android.dialer.R;
     36 import com.android.dialer.list.PhoneFavoritesTileAdapter.ContactTileRow;
     37 import com.android.dialer.list.PhoneFavoritesTileAdapter.ViewTypes;
     38 
     39 /**
     40  * A light version of the {@link com.android.contacts.common.list.ContactTileView} that is used in
     41  * Dialtacts for frequently called contacts. Slightly different behavior from superclass when you
     42  * tap it, you want to call the frequently-called number for the contact, even if that is not the
     43  * default number for that contact. This abstract class is the super class to both the row and tile
     44  * view.
     45  */
     46 public abstract class PhoneFavoriteTileView extends ContactTileView {
     47 
     48     private static final String TAG = PhoneFavoriteTileView.class.getSimpleName();
     49     private static final boolean DEBUG = false;
     50 
     51     // These parameters instruct the photo manager to display the default image/letter at 70% of
     52     // its normal size, and vertically offset upwards 14% towards the top of the letter tile, to
     53     // make room for the contact name and number label at the bottom of the image.
     54     private static final float DEFAULT_IMAGE_LETTER_OFFSET = -0.14f;
     55     private static final float DEFAULT_IMAGE_LETTER_SCALE = 0.70f;
     56 
     57     /** Length of all animations in milliseconds. */
     58     private int mAnimationDuration;
     59 
     60     /** The view that holds the front layer of the favorite contact card. */
     61     private View mFavoriteContactCard;
     62     /** The view that holds the background layer of the removal dialogue. */
     63     private View mRemovalDialogue;
     64     /** Undo button for undoing favorite removal. */
     65     private View mUndoRemovalButton;
     66     /** The view that holds the list view row. */
     67     protected ContactTileRow mParentRow;
     68     /** View that contains the transparent shadow that is overlaid on top of the contact image. */
     69     private View mShadowOverlay;
     70 
     71     /** Users' most frequent phone number. */
     72     private String mPhoneNumberString;
     73 
     74     /** Custom gesture detector.*/
     75     protected GestureDetector mGestureDetector;
     76 
     77     // Dummy clip data object that is attached to drag shadows so that text views
     78     // don't crash with an NPE if the drag shadow is released in their bounds
     79     private static final ClipData EMPTY_CLIP_DATA = ClipData.newPlainText("", "");
     80 
     81     public PhoneFavoriteTileView(Context context, AttributeSet attrs) {
     82         super(context, attrs);
     83         mAnimationDuration = context.getResources().getInteger(R.integer.fade_duration);
     84     }
     85 
     86     public ContactTileRow getParentRow() {
     87         return mParentRow;
     88     }
     89 
     90     @Override
     91     protected void onFinishInflate() {
     92         super.onFinishInflate();
     93         mShadowOverlay = findViewById(R.id.shadow_overlay);
     94         mFavoriteContactCard = findViewById(R.id.contact_favorite_card);
     95         mRemovalDialogue = findViewById(R.id.favorite_remove_dialogue);
     96         mUndoRemovalButton = findViewById(R.id.favorite_remove_undo_button);
     97 
     98         mUndoRemovalButton.setOnClickListener(new OnClickListener() {
     99             @Override
    100             public void onClick(View view) {
    101                 undoRemove();
    102             }
    103         });
    104 
    105         setOnLongClickListener(new OnLongClickListener() {
    106             @Override
    107             public boolean onLongClick(View v) {
    108                 final PhoneFavoriteTileView view = (PhoneFavoriteTileView) v;
    109                 // NOTE The drag shadow is handled in the ListView.
    110                 if (view instanceof PhoneFavoriteRegularRowView) {
    111                     final ContactTileRow parent = view.getParentRow();
    112                     // If the view is regular row, start drag the row view.
    113                     // Drag is not available for the item exceeds the PIN_LIMIT.
    114                     if (parent.getRegularRowItemIndex() < PhoneFavoritesTileAdapter.PIN_LIMIT) {
    115                         parent.startDrag(EMPTY_CLIP_DATA, new View.DragShadowBuilder(), null, 0);
    116                     }
    117                 } else {
    118                     // If the view is a tile view, start drag the tile.
    119                     view.startDrag(EMPTY_CLIP_DATA, new View.DragShadowBuilder(), null, 0);
    120                 }
    121                 return true;
    122             }
    123         });
    124     }
    125 
    126     @Override
    127     public void loadFromContact(ContactEntry entry) {
    128         super.loadFromContact(entry);
    129         mPhoneNumberString = null; // ... in case we're reusing the view
    130         if (entry != null) {
    131             // Grab the phone-number to call directly... see {@link onClick()}
    132             mPhoneNumberString = entry.phoneNumber;
    133 
    134             // If this is a blank entry, don't show anything.
    135             // TODO krelease:Just hide the view for now. For this to truly look like an empty row
    136             // the entire ContactTileRow needs to be hidden.
    137             if (entry == ContactEntry.BLANK_ENTRY) {
    138                 setVisibility(View.INVISIBLE);
    139             } else {
    140                 setVisibility(View.VISIBLE);
    141             }
    142         }
    143     }
    144 
    145     public void displayRemovalDialog() {
    146         mRemovalDialogue.setVisibility(VISIBLE);
    147         mRemovalDialogue.setAlpha(0f);
    148         final ObjectAnimator fadeIn = ObjectAnimator.ofFloat(mRemovalDialogue, "alpha",
    149                 1.f).setDuration(mAnimationDuration);
    150 
    151         fadeIn.addListener(new AnimatorListenerAdapter() {
    152             @Override
    153             public void onAnimationStart(Animator animation) {
    154                 mParentRow.setHasTransientState(true);
    155             };
    156 
    157             @Override
    158             public void onAnimationEnd(Animator animation) {
    159                 mParentRow.setHasTransientState(false);
    160             }
    161         });
    162         fadeIn.start();
    163     }
    164 
    165     /**
    166      * Signals the user wants to undo removing the favorite contact.
    167      */
    168     public void undoRemove() {
    169         // Makes the removal dialogue invisible.
    170         mRemovalDialogue.setAlpha(0.0f);
    171         mRemovalDialogue.setVisibility(GONE);
    172 
    173         // Animates back the favorite contact card.
    174         final ObjectAnimator fadeIn = ObjectAnimator.ofFloat(mFavoriteContactCard, "alpha", 1.f).
    175                 setDuration(mAnimationDuration);
    176         final ObjectAnimator moveBack = ObjectAnimator.ofFloat(mFavoriteContactCard, "translationX",
    177                 0.f).setDuration(mAnimationDuration);
    178 
    179         final AnimatorSet animSet = new AnimatorSet();
    180 
    181         animSet.playTogether(fadeIn, moveBack);
    182 
    183         animSet.addListener(new AnimatorListenerAdapter() {
    184             @Override
    185             public void onAnimationStart(Animator animation) {
    186                 mParentRow.setHasTransientState(true);
    187             }
    188             @Override
    189             public void onAnimationEnd(Animator animation) {
    190                 if (mParentRow.getItemViewType() == ViewTypes.FREQUENT) {
    191                     SwipeHelper.setSwipeable(mParentRow, true);
    192                 } else {
    193                     SwipeHelper.setSwipeable(PhoneFavoriteTileView.this, true);
    194                 }
    195                 mParentRow.setHasTransientState(false);
    196             }
    197         });
    198         animSet.start();
    199         // Signals the PhoneFavoritesTileAdapter to undo the potential delete.
    200         mParentRow.getTileAdapter().undoPotentialRemoveEntryIndex();
    201     }
    202 
    203     /**
    204      * Sets up the favorite contact card.
    205      */
    206     public void setupFavoriteContactCard() {
    207         if (mRemovalDialogue != null) {
    208             mRemovalDialogue.setVisibility(GONE);
    209             mRemovalDialogue.setAlpha(0.f);
    210         }
    211         mFavoriteContactCard.setAlpha(1.0f);
    212         mFavoriteContactCard.setTranslationX(0.f);
    213     }
    214 
    215     @Override
    216     protected void onAttachedToWindow() {
    217         mParentRow = (ContactTileRow) getParent();
    218     }
    219 
    220     @Override
    221     protected boolean isDarkTheme() {
    222         return false;
    223     }
    224 
    225     @Override
    226     protected OnClickListener createClickListener() {
    227         return new OnClickListener() {
    228             @Override
    229             public void onClick(View v) {
    230                 // When the removal dialog is present, don't allow a click to call
    231                 if (mListener == null || mRemovalDialogue.isShown()) return;
    232                 if (TextUtils.isEmpty(mPhoneNumberString)) {
    233                     // Copy "superclass" implementation
    234                     mListener.onContactSelected(getLookupUri(), MoreContactUtils
    235                             .getTargetRectFromView(
    236                                     mContext, PhoneFavoriteTileView.this));
    237                 } else {
    238                     // When you tap a frequently-called contact, you want to
    239                     // call them at the number that you usually talk to them
    240                     // at (i.e. the one displayed in the UI), regardless of
    241                     // whether that's their default number.
    242                     mListener.onCallNumberDirectly(mPhoneNumberString);
    243                 }
    244             }
    245         };
    246     }
    247 
    248     @Override
    249     protected DefaultImageRequest getDefaultImageRequest(String displayName, String lookupKey) {
    250         return new DefaultImageRequest(displayName, lookupKey, ContactPhotoManager.TYPE_DEFAULT,
    251                 DEFAULT_IMAGE_LETTER_SCALE, DEFAULT_IMAGE_LETTER_OFFSET);
    252     }
    253 
    254     @Override
    255     protected void configureViewForImage(boolean isDefaultImage) {
    256         // Hide the shadow overlay if the image is a default image (i.e. colored letter tile)
    257         if (mShadowOverlay != null) {
    258             mShadowOverlay.setVisibility(isDefaultImage ? View.GONE : View.VISIBLE);
    259         }
    260     }
    261 }
    262