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 // Placeholder 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