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 */
17package com.android.dialer.list;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.AnimatorSet;
22import android.animation.ObjectAnimator;
23import android.content.ClipData;
24import android.content.Context;
25import android.text.TextUtils;
26import android.util.AttributeSet;
27import android.view.GestureDetector;
28import android.view.View;
29
30import com.android.contacts.common.ContactPhotoManager;
31import com.android.contacts.common.MoreContactUtils;
32import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
33import com.android.contacts.common.list.ContactEntry;
34import com.android.contacts.common.list.ContactTileView;
35import com.android.dialer.R;
36import com.android.dialer.list.PhoneFavoritesTileAdapter.ContactTileRow;
37import 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 */
46public 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