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