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