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