• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.dialer.searchfragment.cp2;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.database.Cursor;
22 import android.net.Uri;
23 import android.provider.ContactsContract.CommonDataKinds.Phone;
24 import android.provider.ContactsContract.Contacts;
25 import android.support.annotation.IntDef;
26 import android.support.annotation.Nullable;
27 import android.support.v7.widget.RecyclerView.ViewHolder;
28 import android.text.TextUtils;
29 import android.view.View;
30 import android.view.View.OnClickListener;
31 import android.widget.ImageView;
32 import android.widget.QuickContactBadge;
33 import android.widget.TextView;
34 import com.android.dialer.common.Assert;
35 import com.android.dialer.contactphoto.ContactPhotoManager;
36 import com.android.dialer.dialercontact.DialerContact;
37 import com.android.dialer.duo.DuoComponent;
38 import com.android.dialer.enrichedcall.EnrichedCallCapabilities;
39 import com.android.dialer.enrichedcall.EnrichedCallComponent;
40 import com.android.dialer.enrichedcall.EnrichedCallManager;
41 import com.android.dialer.lettertile.LetterTileDrawable;
42 import com.android.dialer.searchfragment.common.Projections;
43 import com.android.dialer.searchfragment.common.QueryBoldingUtil;
44 import com.android.dialer.searchfragment.common.R;
45 import com.android.dialer.searchfragment.common.RowClickListener;
46 import com.android.dialer.searchfragment.common.SearchCursor;
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 
50 /** ViewHolder for a contact row. */
51 public final class SearchContactViewHolder extends ViewHolder implements OnClickListener {
52 
53   /** IntDef for the different types of actions that can be shown. */
54   @Retention(RetentionPolicy.SOURCE)
55   @IntDef({
56     CallToAction.NONE,
57     CallToAction.VIDEO_CALL,
58     CallToAction.DUO_CALL,
59     CallToAction.SHARE_AND_CALL
60   })
61   @interface CallToAction {
62     int NONE = 0;
63     int VIDEO_CALL = 1;
64     int DUO_CALL = 2;
65     int SHARE_AND_CALL = 3;
66   }
67 
68   private final RowClickListener listener;
69   private final QuickContactBadge photo;
70   private final TextView nameOrNumberView;
71   private final TextView numberView;
72   private final ImageView callToActionView;
73   private final Context context;
74 
75   private int position;
76   private String number;
77   private DialerContact dialerContact;
78   private @CallToAction int currentAction;
79 
SearchContactViewHolder(View view, RowClickListener listener)80   public SearchContactViewHolder(View view, RowClickListener listener) {
81     super(view);
82     this.listener = listener;
83     view.setOnClickListener(this);
84     photo = view.findViewById(R.id.photo);
85     nameOrNumberView = view.findViewById(R.id.primary);
86     numberView = view.findViewById(R.id.secondary);
87     callToActionView = view.findViewById(R.id.call_to_action);
88     context = view.getContext();
89   }
90 
91   /**
92    * Binds the ViewHolder with a cursor from {@link SearchContactsCursorLoader} with the data found
93    * at the cursors set position.
94    */
bind(SearchCursor cursor, String query)95   public void bind(SearchCursor cursor, String query) {
96     dialerContact = getDialerContact(context, cursor);
97     position = cursor.getPosition();
98     number = cursor.getString(Projections.PHONE_NUMBER);
99     String name = cursor.getString(Projections.DISPLAY_NAME);
100     String label = getLabel(context.getResources(), cursor);
101     String secondaryInfo =
102         TextUtils.isEmpty(label)
103             ? number
104             : context.getString(
105                 com.android.contacts.common.R.string.call_subject_type_and_number, label, number);
106 
107     nameOrNumberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name, context));
108     numberView.setText(QueryBoldingUtil.getNumberWithQueryBolded(query, secondaryInfo));
109     setCallToAction(cursor, query);
110 
111     if (shouldShowPhoto(cursor)) {
112       nameOrNumberView.setVisibility(View.VISIBLE);
113       photo.setVisibility(View.VISIBLE);
114       String photoUri = cursor.getString(Projections.PHOTO_URI);
115       ContactPhotoManager.getInstance(context)
116           .loadDialerThumbnailOrPhoto(
117               photo,
118               getContactUri(cursor),
119               cursor.getLong(Projections.PHOTO_ID),
120               photoUri == null ? null : Uri.parse(photoUri),
121               name,
122               LetterTileDrawable.TYPE_DEFAULT);
123     } else {
124       nameOrNumberView.setVisibility(View.GONE);
125       photo.setVisibility(View.INVISIBLE);
126     }
127   }
128 
129   // Show the contact photo next to only the first number if a contact has multiple numbers
shouldShowPhoto(SearchCursor cursor)130   private boolean shouldShowPhoto(SearchCursor cursor) {
131     int currentPosition = cursor.getPosition();
132     String currentLookupKey = cursor.getString(Projections.LOOKUP_KEY);
133     cursor.moveToPosition(currentPosition - 1);
134 
135     if (!cursor.isHeader() && !cursor.isBeforeFirst()) {
136       String previousLookupKey = cursor.getString(Projections.LOOKUP_KEY);
137       cursor.moveToPosition(currentPosition);
138       return !currentLookupKey.equals(previousLookupKey);
139     }
140     cursor.moveToPosition(currentPosition);
141     return true;
142   }
143 
getContactUri(Cursor cursor)144   private static Uri getContactUri(Cursor cursor) {
145     long contactId = cursor.getLong(Projections.ID);
146     String lookupKey = cursor.getString(Projections.LOOKUP_KEY);
147     return Contacts.getLookupUri(contactId, lookupKey);
148   }
149 
150   // TODO(calderwoodra): handle CNAP and cequint types.
151   // TODO(calderwoodra): unify this into a utility method with CallLogAdapter#getNumberType
getLabel(Resources resources, Cursor cursor)152   private static String getLabel(Resources resources, Cursor cursor) {
153     int numberType = cursor.getInt(Projections.PHONE_TYPE);
154     String numberLabel = cursor.getString(Projections.PHONE_LABEL);
155 
156     // Returns empty label instead of "custom" if the custom label is empty.
157     if (numberType == Phone.TYPE_CUSTOM && TextUtils.isEmpty(numberLabel)) {
158       return "";
159     }
160     return (String) Phone.getTypeLabel(resources, numberType, numberLabel);
161   }
162 
setCallToAction(SearchCursor cursor, String query)163   private void setCallToAction(SearchCursor cursor, String query) {
164     currentAction = getCallToAction(context, cursor, query);
165     switch (currentAction) {
166       case CallToAction.NONE:
167         callToActionView.setVisibility(View.GONE);
168         callToActionView.setOnClickListener(null);
169         break;
170       case CallToAction.SHARE_AND_CALL:
171         callToActionView.setVisibility(View.VISIBLE);
172         callToActionView.setImageDrawable(
173             context.getDrawable(com.android.contacts.common.R.drawable.ic_phone_attach));
174         callToActionView.setContentDescription(
175             context.getString(R.string.description_search_call_and_share));
176         callToActionView.setOnClickListener(this);
177         break;
178       case CallToAction.DUO_CALL:
179       case CallToAction.VIDEO_CALL:
180         callToActionView.setVisibility(View.VISIBLE);
181         callToActionView.setImageDrawable(
182             context.getDrawable(R.drawable.quantum_ic_videocam_white_24));
183         callToActionView.setContentDescription(
184             context.getString(R.string.description_search_video_call));
185         callToActionView.setOnClickListener(this);
186         break;
187       default:
188         throw Assert.createIllegalStateFailException(
189             "Invalid Call to action type: " + currentAction);
190     }
191   }
192 
getCallToAction( Context context, SearchCursor cursor, String query)193   private static @CallToAction int getCallToAction(
194       Context context, SearchCursor cursor, String query) {
195     int carrierPresence = cursor.getInt(Projections.CARRIER_PRESENCE);
196     String number = cursor.getString(Projections.PHONE_NUMBER);
197     if ((carrierPresence & Phone.CARRIER_PRESENCE_VT_CAPABLE) == 1) {
198       return CallToAction.VIDEO_CALL;
199     }
200 
201     if (DuoComponent.get(context).getDuo().isReachable(context, number)) {
202       return CallToAction.DUO_CALL;
203     }
204 
205     EnrichedCallManager manager = EnrichedCallComponent.get(context).getEnrichedCallManager();
206     EnrichedCallCapabilities capabilities = manager.getCapabilities(number);
207     if (capabilities != null && capabilities.isCallComposerCapable()) {
208       return CallToAction.SHARE_AND_CALL;
209     } else if (shouldRequestCapabilities(cursor, capabilities, query)) {
210       manager.requestCapabilities(number);
211     }
212     return CallToAction.NONE;
213   }
214 
215   /**
216    * An RPC is initiated for each number we request capabilities for, so to limit the network load
217    * and latency on slow networks, we only want to request capabilities for potential contacts the
218    * user is interested in calling. The requirements are that:
219    *
220    * <ul>
221    *   <li>The search query must be 3 or more characters; OR
222    *   <li>There must be 4 or fewer contacts listed in the cursor.
223    * </ul>
224    */
shouldRequestCapabilities( SearchCursor cursor, @Nullable EnrichedCallCapabilities capabilities, @Nullable String query)225   private static boolean shouldRequestCapabilities(
226       SearchCursor cursor,
227       @Nullable EnrichedCallCapabilities capabilities,
228       @Nullable String query) {
229     if (capabilities != null) {
230       return false;
231     }
232 
233     if (query != null && query.length() >= 3) {
234       return true;
235     }
236 
237     // TODO(calderwoodra): implement SearchCursor#getHeaderCount
238     if (cursor.getCount() <= 5) { // 4 contacts + 1 header row element
239       return true;
240     }
241     return false;
242   }
243 
244   @Override
onClick(View view)245   public void onClick(View view) {
246     if (view == callToActionView) {
247       switch (currentAction) {
248         case CallToAction.SHARE_AND_CALL:
249           listener.openCallAndShare(dialerContact);
250           break;
251         case CallToAction.VIDEO_CALL:
252           listener.placeVideoCall(number, position);
253           break;
254         case CallToAction.DUO_CALL:
255           listener.placeDuoCall(number);
256           break;
257         case CallToAction.NONE:
258         default:
259           throw Assert.createIllegalStateFailException(
260               "Invalid Call to action type: " + currentAction);
261       }
262     } else {
263       listener.placeVoiceCall(number, position);
264     }
265   }
266 
getDialerContact(Context context, Cursor cursor)267   private static DialerContact getDialerContact(Context context, Cursor cursor) {
268     DialerContact.Builder contact = DialerContact.newBuilder();
269     String displayName = cursor.getString(Projections.DISPLAY_NAME);
270     String number = cursor.getString(Projections.PHONE_NUMBER);
271     Uri contactUri =
272         Contacts.getLookupUri(
273             cursor.getLong(Projections.CONTACT_ID), cursor.getString(Projections.LOOKUP_KEY));
274 
275     contact
276         .setNumber(number)
277         .setPhotoId(cursor.getLong(Projections.PHOTO_ID))
278         .setContactType(LetterTileDrawable.TYPE_DEFAULT)
279         .setNameOrNumber(displayName)
280         .setNumberLabel(
281             Phone.getTypeLabel(
282                     context.getResources(),
283                     cursor.getInt(Projections.PHONE_TYPE),
284                     cursor.getString(Projections.PHONE_LABEL))
285                 .toString());
286 
287     String photoUri = cursor.getString(Projections.PHOTO_URI);
288     if (photoUri != null) {
289       contact.setPhotoUri(photoUri);
290     }
291 
292     if (contactUri != null) {
293       contact.setContactUri(contactUri.toString());
294     }
295 
296     if (!TextUtils.isEmpty(displayName)) {
297       contact.setDisplayNumber(number);
298     }
299 
300     return contact.build();
301   }
302 }
303