1 /* 2 * Copyright (C) 2011 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.calllog; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.content.Intent; 22 import android.net.Uri; 23 import android.provider.CallLog.Calls; 24 import android.provider.ContactsContract.CommonDataKinds.Phone; 25 import android.support.v7.widget.CardView; 26 import android.support.v7.widget.RecyclerView; 27 import android.telecom.PhoneAccountHandle; 28 import android.text.TextUtils; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.ViewStub; 32 import android.view.ViewTreeObserver; 33 import android.widget.QuickContactBadge; 34 import android.widget.ImageView; 35 import android.widget.TextView; 36 37 import com.android.contacts.common.CallUtil; 38 import com.android.contacts.common.ContactPhotoManager; 39 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; 40 import com.android.contacts.common.testing.NeededForTesting; 41 import com.android.contacts.common.util.UriUtils; 42 import com.android.dialer.R; 43 import com.android.dialer.calllog.CallLogAsyncTaskUtil; 44 import com.android.dialer.util.DialerUtils; 45 import com.android.dialer.util.PhoneNumberUtil; 46 import com.android.dialer.voicemail.VoicemailPlaybackPresenter; 47 import com.android.dialer.voicemail.VoicemailPlaybackLayout; 48 49 /** 50 * This is an object containing references to views contained by the call log list item. This 51 * improves performance by reducing the frequency with which we need to find views by IDs. 52 * 53 * This object also contains UI logic pertaining to the view, to isolate it from the CallLogAdapter. 54 */ 55 public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder 56 implements View.OnClickListener { 57 58 /** The root view of the call log list item */ 59 public final View rootView; 60 /** The quick contact badge for the contact. */ 61 public final QuickContactBadge quickContactView; 62 /** The primary action view of the entry. */ 63 public final View primaryActionView; 64 /** The details of the phone call. */ 65 public final PhoneCallDetailsViews phoneCallDetailsViews; 66 /** The text of the header for a day grouping. */ 67 public final TextView dayGroupHeader; 68 /** The view containing the details for the call log row, including the action buttons. */ 69 public final CardView callLogEntryView; 70 /** The actionable view which places a call to the number corresponding to the call log row. */ 71 public final ImageView primaryActionButtonView; 72 73 /** The view containing call log item actions. Null until the ViewStub is inflated. */ 74 public View actionsView; 75 /** The button views below are assigned only when the action section is expanded. */ 76 public VoicemailPlaybackLayout voicemailPlaybackView; 77 public View callButtonView; 78 public View videoCallButtonView; 79 public View createNewContactButtonView; 80 public View addToExistingContactButtonView; 81 public View sendMessageView; 82 public View detailsButtonView; 83 84 /** 85 * The row Id for the first call associated with the call log entry. Used as a key for the 86 * map used to track which call log entries have the action button section expanded. 87 */ 88 public long rowId; 89 90 /** 91 * The call Ids for the calls represented by the current call log entry. Used when the user 92 * deletes a call log entry. 93 */ 94 public long[] callIds; 95 96 /** 97 * The callable phone number for the current call log entry. Cached here as the call back 98 * intent is set only when the actions ViewStub is inflated. 99 */ 100 public String number; 101 102 /** 103 * The phone number presentation for the current call log entry. Cached here as the call back 104 * intent is set only when the actions ViewStub is inflated. 105 */ 106 public int numberPresentation; 107 108 /** 109 * The type of call for the current call log entry. Cached here as the call back 110 * intent is set only when the actions ViewStub is inflated. 111 */ 112 public int callType; 113 114 /** 115 * The account for the current call log entry. Cached here as the call back 116 * intent is set only when the actions ViewStub is inflated. 117 */ 118 public PhoneAccountHandle accountHandle; 119 120 /** 121 * If the call has an associated voicemail message, the URI of the voicemail message for 122 * playback. Cached here as the voicemail intent is only set when the actions ViewStub is 123 * inflated. 124 */ 125 public String voicemailUri; 126 127 /** 128 * The name or number associated with the call. Cached here for use when setting content 129 * descriptions on buttons in the actions ViewStub when it is inflated. 130 */ 131 public CharSequence nameOrNumber; 132 133 /** 134 * The contact info for the contact displayed in this list item. 135 */ 136 public ContactInfo info; 137 138 private static final int VOICEMAIL_TRANSCRIPTION_MAX_LINES = 10; 139 140 private final Context mContext; 141 private final TelecomCallLogCache mTelecomCallLogCache; 142 private final CallLogListItemHelper mCallLogListItemHelper; 143 private final VoicemailPlaybackPresenter mVoicemailPlaybackPresenter; 144 145 private final int mPhotoSize; 146 147 private View.OnClickListener mExpandCollapseListener; 148 private boolean mVoicemailPrimaryActionButtonClicked; 149 150 private CallLogListItemViewHolder( 151 Context context, 152 View.OnClickListener expandCollapseListener, 153 TelecomCallLogCache telecomCallLogCache, 154 CallLogListItemHelper callLogListItemHelper, 155 VoicemailPlaybackPresenter voicemailPlaybackPresenter, 156 View rootView, 157 QuickContactBadge quickContactView, 158 View primaryActionView, 159 PhoneCallDetailsViews phoneCallDetailsViews, 160 CardView callLogEntryView, 161 TextView dayGroupHeader, 162 ImageView primaryActionButtonView) { 163 super(rootView); 164 165 mContext = context; 166 mExpandCollapseListener = expandCollapseListener; 167 mTelecomCallLogCache = telecomCallLogCache; 168 mCallLogListItemHelper = callLogListItemHelper; 169 mVoicemailPlaybackPresenter = voicemailPlaybackPresenter; 170 171 this.rootView = rootView; 172 this.quickContactView = quickContactView; 173 this.primaryActionView = primaryActionView; 174 this.phoneCallDetailsViews = phoneCallDetailsViews; 175 this.callLogEntryView = callLogEntryView; 176 this.dayGroupHeader = dayGroupHeader; 177 this.primaryActionButtonView = primaryActionButtonView; 178 179 Resources resources = mContext.getResources(); 180 mPhotoSize = mContext.getResources().getDimensionPixelSize(R.dimen.contact_photo_size); 181 182 // Set text height to false on the TextViews so they don't have extra padding. 183 phoneCallDetailsViews.nameView.setElegantTextHeight(false); 184 phoneCallDetailsViews.callLocationAndDate.setElegantTextHeight(false); 185 186 quickContactView.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE); 187 188 primaryActionButtonView.setOnClickListener(this); 189 primaryActionView.setOnClickListener(mExpandCollapseListener); 190 } 191 192 public static CallLogListItemViewHolder create( 193 View view, 194 Context context, 195 View.OnClickListener expandCollapseListener, 196 TelecomCallLogCache telecomCallLogCache, 197 CallLogListItemHelper callLogListItemHelper, 198 VoicemailPlaybackPresenter voicemailPlaybackPresenter) { 199 200 return new CallLogListItemViewHolder( 201 context, 202 expandCollapseListener, 203 telecomCallLogCache, 204 callLogListItemHelper, 205 voicemailPlaybackPresenter, 206 view, 207 (QuickContactBadge) view.findViewById(R.id.quick_contact_photo), 208 view.findViewById(R.id.primary_action_view), 209 PhoneCallDetailsViews.fromView(view), 210 (CardView) view.findViewById(R.id.call_log_row), 211 (TextView) view.findViewById(R.id.call_log_day_group_label), 212 (ImageView) view.findViewById(R.id.primary_action_button)); 213 } 214 215 /** 216 * Configures the action buttons in the expandable actions ViewStub. The ViewStub is not 217 * inflated during initial binding, so click handlers, tags and accessibility text must be set 218 * here, if necessary. 219 * 220 * @param callLogItem The call log list item view. 221 */ 222 public void inflateActionViewStub() { 223 ViewStub stub = (ViewStub) rootView.findViewById(R.id.call_log_entry_actions_stub); 224 if (stub != null) { 225 actionsView = (ViewGroup) stub.inflate(); 226 227 voicemailPlaybackView = (VoicemailPlaybackLayout) actionsView 228 .findViewById(R.id.voicemail_playback_layout); 229 230 callButtonView = actionsView.findViewById(R.id.call_action); 231 callButtonView.setOnClickListener(this); 232 233 videoCallButtonView = actionsView.findViewById(R.id.video_call_action); 234 videoCallButtonView.setOnClickListener(this); 235 236 createNewContactButtonView = actionsView.findViewById(R.id.create_new_contact_action); 237 createNewContactButtonView.setOnClickListener(this); 238 239 addToExistingContactButtonView = 240 actionsView.findViewById(R.id.add_to_existing_contact_action); 241 addToExistingContactButtonView.setOnClickListener(this); 242 243 sendMessageView = actionsView.findViewById(R.id.send_message_action); 244 sendMessageView.setOnClickListener(this); 245 246 detailsButtonView = actionsView.findViewById(R.id.details_action); 247 detailsButtonView.setOnClickListener(this); 248 } 249 250 bindActionButtons(); 251 } 252 253 private void updatePrimaryActionButton(boolean isExpanded) { 254 if (!TextUtils.isEmpty(voicemailUri)) { 255 // Treat as voicemail list item; show play button if not expanded. 256 if (!isExpanded) { 257 primaryActionButtonView.setImageResource(R.drawable.ic_play_arrow_24dp); 258 primaryActionButtonView.setVisibility(View.VISIBLE); 259 } else { 260 primaryActionButtonView.setVisibility(View.GONE); 261 } 262 } else { 263 // Treat as normal list item; show call button, if possible. 264 boolean canPlaceCallToNumber = 265 PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation); 266 267 if (canPlaceCallToNumber) { 268 boolean isVoicemailNumber = 269 mTelecomCallLogCache.isVoicemailNumber(accountHandle, number); 270 if (isVoicemailNumber) { 271 // Call to generic voicemail number, in case there are multiple accounts. 272 primaryActionButtonView.setTag( 273 IntentProvider.getReturnVoicemailCallIntentProvider()); 274 } else { 275 primaryActionButtonView.setTag( 276 IntentProvider.getReturnCallIntentProvider(number)); 277 } 278 279 primaryActionButtonView.setContentDescription(TextUtils.expandTemplate( 280 mContext.getString(R.string.description_call_action), 281 nameOrNumber)); 282 primaryActionButtonView.setImageResource(R.drawable.ic_call_24dp); 283 primaryActionButtonView.setVisibility(View.VISIBLE); 284 } else { 285 primaryActionButtonView.setTag(null); 286 primaryActionButtonView.setVisibility(View.GONE); 287 } 288 } 289 } 290 291 /** 292 * Binds text titles, click handlers and intents to the voicemail, details and callback action 293 * buttons. 294 */ 295 private void bindActionButtons() { 296 boolean canPlaceCallToNumber = PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation); 297 298 if (!TextUtils.isEmpty(voicemailUri) && canPlaceCallToNumber) { 299 callButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number)); 300 ((TextView) callButtonView.findViewById(R.id.call_action_text)) 301 .setText(TextUtils.expandTemplate( 302 mContext.getString(R.string.call_log_action_call), 303 nameOrNumber)); 304 callButtonView.setVisibility(View.VISIBLE); 305 } else { 306 callButtonView.setVisibility(View.GONE); 307 } 308 309 // If one of the calls had video capabilities, show the video call button. 310 if (mTelecomCallLogCache.isVideoEnabled() && canPlaceCallToNumber && 311 phoneCallDetailsViews.callTypeIcons.isVideoShown()) { 312 videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number)); 313 videoCallButtonView.setVisibility(View.VISIBLE); 314 } else { 315 videoCallButtonView.setVisibility(View.GONE); 316 } 317 318 // For voicemail calls, show the voicemail playback layout; hide otherwise. 319 if (callType == Calls.VOICEMAIL_TYPE && mVoicemailPlaybackPresenter != null) { 320 voicemailPlaybackView.setVisibility(View.VISIBLE); 321 322 Uri uri = Uri.parse(voicemailUri); 323 mVoicemailPlaybackPresenter.setPlaybackView( 324 voicemailPlaybackView, uri, mVoicemailPrimaryActionButtonClicked); 325 mVoicemailPrimaryActionButtonClicked = false; 326 327 CallLogAsyncTaskUtil.markVoicemailAsRead(mContext, uri); 328 } else { 329 voicemailPlaybackView.setVisibility(View.GONE); 330 } 331 332 detailsButtonView.setVisibility(View.VISIBLE); 333 detailsButtonView.setTag( 334 IntentProvider.getCallDetailIntentProvider(rowId, callIds, null)); 335 336 if (info != null && UriUtils.isEncodedContactUri(info.lookupUri)) { 337 createNewContactButtonView.setTag(IntentProvider.getAddContactIntentProvider( 338 info.lookupUri, info.name, info.number, info.type, true /* isNewContact */)); 339 createNewContactButtonView.setVisibility(View.VISIBLE); 340 341 addToExistingContactButtonView.setTag(IntentProvider.getAddContactIntentProvider( 342 info.lookupUri, info.name, info.number, info.type, false /* isNewContact */)); 343 addToExistingContactButtonView.setVisibility(View.VISIBLE); 344 } else { 345 createNewContactButtonView.setVisibility(View.GONE); 346 addToExistingContactButtonView.setVisibility(View.GONE); 347 } 348 349 sendMessageView.setTag(IntentProvider.getSendSmsIntentProvider(number)); 350 351 mCallLogListItemHelper.setActionContentDescriptions(this); 352 } 353 354 /** 355 * Show or hide the action views, such as voicemail, details, and add contact. 356 * 357 * If the action views have never been shown yet for this view, inflate the view stub. 358 */ 359 public void showActions(boolean show) { 360 expandVoicemailTranscriptionView(show); 361 362 if (show) { 363 // Inflate the view stub if necessary, and wire up the event handlers. 364 inflateActionViewStub(); 365 366 actionsView.setVisibility(View.VISIBLE); 367 actionsView.setAlpha(1.0f); 368 } else { 369 // When recycling a view, it is possible the actionsView ViewStub was previously 370 // inflated so we should hide it in this case. 371 if (actionsView != null) { 372 actionsView.setVisibility(View.GONE); 373 } 374 } 375 376 updatePrimaryActionButton(show); 377 } 378 379 public void expandVoicemailTranscriptionView(boolean isExpanded) { 380 if (callType != Calls.VOICEMAIL_TYPE) { 381 return; 382 } 383 384 final TextView view = phoneCallDetailsViews.voicemailTranscriptionView; 385 if (TextUtils.isEmpty(view.getText())) { 386 return; 387 } 388 view.setMaxLines(isExpanded ? VOICEMAIL_TRANSCRIPTION_MAX_LINES : 1); 389 view.setSingleLine(!isExpanded); 390 } 391 392 public void setPhoto(long photoId, Uri photoUri, Uri contactUri, String displayName, 393 boolean isVoicemail, boolean isBusiness) { 394 quickContactView.assignContactUri(contactUri); 395 quickContactView.setOverlay(null); 396 397 int contactType = ContactPhotoManager.TYPE_DEFAULT; 398 if (isVoicemail) { 399 contactType = ContactPhotoManager.TYPE_VOICEMAIL; 400 } else if (isBusiness) { 401 contactType = ContactPhotoManager.TYPE_BUSINESS; 402 } 403 404 String lookupKey = null; 405 if (contactUri != null) { 406 lookupKey = ContactInfoHelper.getLookupKeyFromUri(contactUri); 407 } 408 409 DefaultImageRequest request = new DefaultImageRequest( 410 displayName, lookupKey, contactType, true /* isCircular */); 411 412 if (photoId == 0 && photoUri != null) { 413 ContactPhotoManager.getInstance(mContext).loadPhoto(quickContactView, photoUri, 414 mPhotoSize, false /* darkTheme */, true /* isCircular */, request); 415 } else { 416 ContactPhotoManager.getInstance(mContext).loadThumbnail(quickContactView, photoId, 417 false /* darkTheme */, true /* isCircular */, request); 418 } 419 } 420 421 @Override 422 public void onClick(View view) { 423 if (view.getId() == R.id.primary_action_button && !TextUtils.isEmpty(voicemailUri)) { 424 mVoicemailPrimaryActionButtonClicked = true; 425 mExpandCollapseListener.onClick(primaryActionView); 426 } else { 427 final IntentProvider intentProvider = (IntentProvider) view.getTag(); 428 if (intentProvider != null) { 429 final Intent intent = intentProvider.getIntent(mContext); 430 // See IntentProvider.getCallDetailIntentProvider() for why this may be null. 431 if (intent != null) { 432 DialerUtils.startActivityWithErrorToast(mContext, intent); 433 } 434 } 435 } 436 } 437 438 @NeededForTesting 439 public static CallLogListItemViewHolder createForTest(Context context) { 440 Resources resources = context.getResources(); 441 TelecomCallLogCache telecomCallLogCache = new TelecomCallLogCache(context); 442 PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper( 443 context, resources, telecomCallLogCache); 444 445 CallLogListItemViewHolder viewHolder = new CallLogListItemViewHolder( 446 context, 447 null /* expandCollapseListener */, 448 telecomCallLogCache, 449 new CallLogListItemHelper(phoneCallDetailsHelper, resources, telecomCallLogCache), 450 null /* voicemailPlaybackPresenter */, 451 new View(context), 452 new QuickContactBadge(context), 453 new View(context), 454 PhoneCallDetailsViews.createForTest(context), 455 new CardView(context), 456 new TextView(context), 457 new ImageView(context)); 458 viewHolder.detailsButtonView = new TextView(context); 459 viewHolder.actionsView = new View(context); 460 viewHolder.voicemailPlaybackView = new VoicemailPlaybackLayout(context); 461 462 return viewHolder; 463 } 464 } 465