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.app.list;
18
19import android.content.ClipData;
20import android.content.Context;
21import android.net.Uri;
22import android.provider.ContactsContract.PinnedPositions;
23import android.text.TextUtils;
24import android.util.AttributeSet;
25import android.view.View;
26import android.widget.ImageView;
27import com.android.contacts.common.MoreContactUtils;
28import com.android.contacts.common.list.ContactEntry;
29import com.android.contacts.common.list.ContactTileView;
30import com.android.contacts.common.model.ContactLoader;
31import com.android.dialer.app.R;
32import com.android.dialer.callintent.CallInitiationType;
33import com.android.dialer.callintent.CallSpecificAppData;
34import com.android.dialer.callintent.SpeedDialContactType;
35import com.android.dialer.contactphoto.ContactPhotoManager.DefaultImageRequest;
36import com.android.dialer.lettertile.LetterTileDrawable;
37import com.android.dialer.logging.InteractionEvent;
38import com.android.dialer.logging.Logger;
39
40/**
41 * A light version of the {@link com.android.contacts.common.list.ContactTileView} that is used in
42 * Dialtacts for frequently called contacts. Slightly different behavior from superclass when you
43 * tap it, you want to call the frequently-called number for the contact, even if that is not the
44 * default number for that contact. This abstract class is the super class to both the row and tile
45 * view.
46 */
47public abstract class PhoneFavoriteTileView extends ContactTileView {
48
49  // Constant to pass to the drag event so that the drag action only happens when a phone favorite
50  // tile is long pressed.
51  static final String DRAG_PHONE_FAVORITE_TILE = "PHONE_FAVORITE_TILE";
52  private static final String TAG = PhoneFavoriteTileView.class.getSimpleName();
53  // These parameters instruct the photo manager to display the default image/letter at 70% of
54  // its normal size, and vertically offset upwards 12% towards the top of the letter tile, to
55  // make room for the contact name and number label at the bottom of the image.
56  private static final float DEFAULT_IMAGE_LETTER_OFFSET = -0.12f;
57  private static final float DEFAULT_IMAGE_LETTER_SCALE = 0.70f;
58  // Dummy clip data object that is attached to drag shadows so that text views
59  // don't crash with an NPE if the drag shadow is released in their bounds
60  private static final ClipData EMPTY_CLIP_DATA = ClipData.newPlainText("", "");
61  /** View that contains the transparent shadow that is overlaid on top of the contact image. */
62  private View shadowOverlay;
63  /** Users' most frequent phone number. */
64  private String phoneNumberString;
65
66  private boolean isPinned;
67  private boolean isStarred;
68  private int position = -1;
69
70  public PhoneFavoriteTileView(Context context, AttributeSet attrs) {
71    super(context, attrs);
72  }
73
74  @Override
75  protected void onFinishInflate() {
76    super.onFinishInflate();
77    shadowOverlay = findViewById(R.id.shadow_overlay);
78
79    setOnLongClickListener(
80        new OnLongClickListener() {
81          @Override
82          public boolean onLongClick(View v) {
83            final PhoneFavoriteTileView view = (PhoneFavoriteTileView) v;
84            // NOTE The drag shadow is handled in the ListView.
85            view.startDrag(
86                EMPTY_CLIP_DATA, new View.DragShadowBuilder(), DRAG_PHONE_FAVORITE_TILE, 0);
87            return true;
88          }
89        });
90  }
91
92  @Override
93  public void loadFromContact(ContactEntry entry) {
94    super.loadFromContact(entry);
95    // Set phone number to null in case we're reusing the view.
96    phoneNumberString = null;
97    isPinned = (entry.pinned != PinnedPositions.UNPINNED);
98    isStarred = entry.isFavorite;
99    if (entry != null) {
100      sendViewNotification(getContext(), entry.lookupUri);
101      // Grab the phone-number to call directly. See {@link onClick()}.
102      phoneNumberString = entry.phoneNumber;
103
104      // If this is a blank entry, don't show anything. For this to truly look like an empty row
105      // the entire ContactTileRow needs to be hidden.
106      if (entry == ContactEntry.BLANK_ENTRY) {
107        setVisibility(View.INVISIBLE);
108      } else {
109        final ImageView starIcon = (ImageView) findViewById(R.id.contact_star_icon);
110        starIcon.setVisibility(entry.isFavorite ? View.VISIBLE : View.GONE);
111        setVisibility(View.VISIBLE);
112      }
113    }
114  }
115
116  @Override
117  protected boolean isDarkTheme() {
118    return false;
119  }
120
121  @Override
122  protected OnClickListener createClickListener() {
123    return new OnClickListener() {
124      @Override
125      public void onClick(View v) {
126        if (mListener == null) {
127          return;
128        }
129
130        CallSpecificAppData.Builder callSpecificAppData =
131            CallSpecificAppData.newBuilder()
132                .setAllowAssistedDialing(true)
133                .setCallInitiationType(CallInitiationType.Type.SPEED_DIAL)
134                .setSpeedDialContactPosition(position);
135        if (isStarred) {
136          callSpecificAppData.addSpeedDialContactType(SpeedDialContactType.Type.STARRED_CONTACT);
137        } else {
138          callSpecificAppData.addSpeedDialContactType(SpeedDialContactType.Type.FREQUENT_CONTACT);
139        }
140        if (isPinned) {
141          callSpecificAppData.addSpeedDialContactType(SpeedDialContactType.Type.PINNED_CONTACT);
142        }
143
144        if (TextUtils.isEmpty(phoneNumberString)) {
145          // Don't set performance report now, since user may spend some time on picking a number
146
147          // Copy "superclass" implementation
148          Logger.get(getContext())
149              .logInteraction(InteractionEvent.Type.SPEED_DIAL_CLICK_CONTACT_WITH_AMBIGUOUS_NUMBER);
150          mListener.onContactSelected(
151              getLookupUri(),
152              MoreContactUtils.getTargetRectFromView(PhoneFavoriteTileView.this),
153              callSpecificAppData.build());
154        } else {
155          // When you tap a frequently-called contact, you want to
156          // call them at the number that you usually talk to them
157          // at (i.e. the one displayed in the UI), regardless of
158          // whether that's their default number.
159          mListener.onCallNumberDirectly(phoneNumberString, callSpecificAppData.build());
160        }
161      }
162    };
163  }
164
165  @Override
166  protected DefaultImageRequest getDefaultImageRequest(String displayName, String lookupKey) {
167    return new DefaultImageRequest(
168        displayName,
169        lookupKey,
170        LetterTileDrawable.TYPE_DEFAULT,
171        DEFAULT_IMAGE_LETTER_SCALE,
172        DEFAULT_IMAGE_LETTER_OFFSET,
173        false);
174  }
175
176  @Override
177  protected void configureViewForImage(boolean isDefaultImage) {
178    // Hide the shadow overlay if the image is a default image (i.e. colored letter tile)
179    if (shadowOverlay != null) {
180      shadowOverlay.setVisibility(isDefaultImage ? View.GONE : View.VISIBLE);
181    }
182  }
183
184  @Override
185  protected boolean isContactPhotoCircular() {
186    // Unlike Contacts' tiles, the Dialer's favorites tiles are square.
187    return false;
188  }
189
190  public void setPosition(int position) {
191    this.position = position;
192  }
193
194  private ContactLoader loader;
195
196  /**
197   * Send a notification using a {@link ContactLoader} to inform the sync adapter that we are
198   * viewing a particular contact, so that it can download the high-res photo.
199   */
200  private void sendViewNotification(Context context, Uri contactUri) {
201    if (loader != null) {
202      // Cancels the current load if it's running and clears up any memory if it's using any.
203      loader.reset();
204    }
205    loader = new ContactLoader(context, contactUri, true /* postViewNotification */);
206    // Immediately release anything we're holding in memory
207    loader.registerListener(0, (loader1, contact) -> loader.reset());
208    loader.startLoading();
209  }
210}
211