1 /* 2 * Copyright (C) 2016 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.incallui.answer.impl; 18 19 import android.Manifest.permission; 20 import android.animation.Animator; 21 import android.animation.AnimatorListenerAdapter; 22 import android.animation.AnimatorSet; 23 import android.animation.ObjectAnimator; 24 import android.annotation.SuppressLint; 25 import android.content.Context; 26 import android.content.pm.PackageManager; 27 import android.location.Location; 28 import android.net.Uri; 29 import android.os.Bundle; 30 import android.os.Handler; 31 import android.os.Looper; 32 import android.os.Trace; 33 import android.support.annotation.DrawableRes; 34 import android.support.annotation.FloatRange; 35 import android.support.annotation.NonNull; 36 import android.support.annotation.Nullable; 37 import android.support.annotation.StringRes; 38 import android.support.annotation.VisibleForTesting; 39 import android.support.v4.app.Fragment; 40 import android.text.TextUtils; 41 import android.transition.TransitionManager; 42 import android.view.LayoutInflater; 43 import android.view.View; 44 import android.view.View.AccessibilityDelegate; 45 import android.view.View.OnClickListener; 46 import android.view.ViewGroup; 47 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 48 import android.view.accessibility.AccessibilityEvent; 49 import android.view.accessibility.AccessibilityNodeInfo; 50 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 51 import android.widget.ImageView; 52 import android.widget.LinearLayout; 53 import com.android.dialer.common.Assert; 54 import com.android.dialer.common.FragmentUtils; 55 import com.android.dialer.common.LogUtil; 56 import com.android.dialer.common.MathUtil; 57 import com.android.dialer.logging.DialerImpression; 58 import com.android.dialer.logging.Logger; 59 import com.android.dialer.multimedia.MultimediaData; 60 import com.android.dialer.telecom.TelecomUtil; 61 import com.android.dialer.util.ViewUtil; 62 import com.android.incallui.answer.impl.CreateCustomSmsDialogFragment.CreateCustomSmsHolder; 63 import com.android.incallui.answer.impl.SmsBottomSheetFragment.SmsSheetHolder; 64 import com.android.incallui.answer.impl.affordance.SwipeButtonHelper.Callback; 65 import com.android.incallui.answer.impl.affordance.SwipeButtonView; 66 import com.android.incallui.answer.impl.answermethod.AnswerMethod; 67 import com.android.incallui.answer.impl.answermethod.AnswerMethodFactory; 68 import com.android.incallui.answer.impl.answermethod.AnswerMethodHolder; 69 import com.android.incallui.answer.impl.utils.Interpolators; 70 import com.android.incallui.answer.protocol.AnswerScreen; 71 import com.android.incallui.answer.protocol.AnswerScreenDelegate; 72 import com.android.incallui.answer.protocol.AnswerScreenDelegateFactory; 73 import com.android.incallui.call.state.DialerCallState; 74 import com.android.incallui.contactgrid.ContactGridManager; 75 import com.android.incallui.incall.protocol.ContactPhotoType; 76 import com.android.incallui.incall.protocol.InCallScreen; 77 import com.android.incallui.incall.protocol.InCallScreenDelegate; 78 import com.android.incallui.incall.protocol.InCallScreenDelegateFactory; 79 import com.android.incallui.incall.protocol.PrimaryCallState; 80 import com.android.incallui.incall.protocol.PrimaryInfo; 81 import com.android.incallui.incall.protocol.SecondaryInfo; 82 import com.android.incallui.incalluilock.InCallUiLock; 83 import com.android.incallui.maps.MapsComponent; 84 import com.android.incallui.sessiondata.AvatarPresenter; 85 import com.android.incallui.sessiondata.MultimediaFragment; 86 import com.android.incallui.speakeasy.Annotations.SpeakEasyChipResourceId; 87 import com.android.incallui.speakeasy.SpeakEasyComponent; 88 import com.android.incallui.util.AccessibilityUtil; 89 import com.android.incallui.video.protocol.VideoCallScreen; 90 import com.android.incallui.videotech.utils.VideoUtils; 91 import com.google.common.base.Optional; 92 import java.util.ArrayList; 93 import java.util.List; 94 import java.util.Objects; 95 96 /** The new version of the incoming call screen. */ 97 @SuppressLint("ClickableViewAccessibility") 98 public class AnswerFragment extends Fragment 99 implements AnswerScreen, 100 InCallScreen, 101 SmsSheetHolder, 102 CreateCustomSmsHolder, 103 AnswerMethodHolder, 104 MultimediaFragment.Holder { 105 106 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 107 static final String ARG_CALL_ID = "call_id"; 108 109 static final String ARG_IS_RTT_CALL = "is_rtt_call"; 110 111 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 112 static final String ARG_IS_VIDEO_CALL = "is_video_call"; 113 114 static final String ARG_ALLOW_ANSWER_AND_RELEASE = "allow_answer_and_release"; 115 116 static final String ARG_HAS_CALL_ON_HOLD = "has_call_on_hold"; 117 118 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 119 static final String ARG_IS_VIDEO_UPGRADE_REQUEST = "is_video_upgrade_request"; 120 121 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 122 static final String ARG_IS_SELF_MANAGED_CAMERA = "is_self_managed_camera"; 123 124 static final String ARG_ALLOW_SPEAK_EASY = "allow_speak_easy"; 125 126 private static final String STATE_HAS_ANIMATED_ENTRY = "hasAnimated"; 127 128 private static final int HINT_SECONDARY_SHOW_DURATION_MILLIS = 5000; 129 private static final float ANIMATE_LERP_PROGRESS = 0.5f; 130 private static final int STATUS_BAR_DISABLE_RECENT = 0x01000000; 131 private static final int STATUS_BAR_DISABLE_HOME = 0x00200000; 132 private static final int STATUS_BAR_DISABLE_BACK = 0x00400000; 133 fadeToward(View view, float newAlpha)134 private static void fadeToward(View view, float newAlpha) { 135 view.setAlpha(MathUtil.lerp(view.getAlpha(), newAlpha, ANIMATE_LERP_PROGRESS)); 136 } 137 scaleToward(View view, float newScale)138 private static void scaleToward(View view, float newScale) { 139 view.setScaleX(MathUtil.lerp(view.getScaleX(), newScale, ANIMATE_LERP_PROGRESS)); 140 view.setScaleY(MathUtil.lerp(view.getScaleY(), newScale, ANIMATE_LERP_PROGRESS)); 141 } 142 143 private AnswerScreenDelegate answerScreenDelegate; 144 private InCallScreenDelegate inCallScreenDelegate; 145 146 private View importanceBadge; 147 private SwipeButtonView secondaryButton; 148 private SwipeButtonView answerAndReleaseButton; 149 private AffordanceHolderLayout affordanceHolderLayout; 150 private LinearLayout chipContainer; 151 // Use these flags to prevent user from clicking accept/reject buttons multiple times. 152 // We use separate flags because in some rare cases accepting a call may fail to join the room, 153 // and then user is stuck in the incoming call view until it times out. Two flags at least give 154 // the user a chance to get out of the CallActivity. 155 private boolean buttonAcceptClicked; 156 private boolean buttonRejectClicked; 157 private boolean hasAnimatedEntry; 158 private PrimaryInfo primaryInfo = PrimaryInfo.empty(); 159 private PrimaryCallState primaryCallState; 160 private ArrayList<CharSequence> textResponses; 161 private SmsBottomSheetFragment textResponsesFragment; 162 private CreateCustomSmsDialogFragment createCustomSmsDialogFragment; 163 private SecondaryBehavior secondaryBehavior = SecondaryBehavior.REJECT_WITH_SMS; 164 private SecondaryBehavior answerAndReleaseBehavior; 165 private ContactGridManager contactGridManager; 166 private VideoCallScreen answerVideoCallScreen; 167 private Handler handler = new Handler(Looper.getMainLooper()); 168 169 private enum SecondaryBehavior { REJECT_WITH_SMS( R.drawable.quantum_ic_message_white_24, R.string.a11y_description_incoming_call_reject_with_sms, R.string.a11y_incoming_call_reject_with_sms, R.string.call_incoming_swipe_to_decline_with_message)170 REJECT_WITH_SMS( 171 R.drawable.quantum_ic_message_white_24, 172 R.string.a11y_description_incoming_call_reject_with_sms, 173 R.string.a11y_incoming_call_reject_with_sms, 174 R.string.call_incoming_swipe_to_decline_with_message) { 175 @Override 176 public void performAction(AnswerFragment fragment) { 177 fragment.showMessageMenu(); 178 } 179 }, 180 ANSWER_VIDEO_AS_AUDIO( R.drawable.quantum_ic_videocam_off_vd_theme_24, R.string.a11y_description_incoming_call_answer_video_as_audio, R.string.a11y_incoming_call_answer_video_as_audio, R.string.call_incoming_swipe_to_answer_video_as_audio)181 ANSWER_VIDEO_AS_AUDIO( 182 R.drawable.quantum_ic_videocam_off_vd_theme_24, 183 R.string.a11y_description_incoming_call_answer_video_as_audio, 184 R.string.a11y_incoming_call_answer_video_as_audio, 185 R.string.call_incoming_swipe_to_answer_video_as_audio) { 186 @Override 187 public void performAction(AnswerFragment fragment) { 188 fragment.acceptCallByUser(true /* answerVideoAsAudio */); 189 } 190 }, 191 ANSWER_AND_RELEASE( R.drawable.ic_end_answer_32, R.string.a11y_description_incoming_call_answer_and_release, R.string.a11y_incoming_call_answer_and_release, R.string.call_incoming_swipe_to_answer_and_release)192 ANSWER_AND_RELEASE( 193 R.drawable.ic_end_answer_32, 194 R.string.a11y_description_incoming_call_answer_and_release, 195 R.string.a11y_incoming_call_answer_and_release, 196 R.string.call_incoming_swipe_to_answer_and_release) { 197 @Override 198 public void performAction(AnswerFragment fragment) { 199 fragment.performAnswerAndRelease(); 200 } 201 }; 202 203 @DrawableRes public int icon; 204 @StringRes public final int contentDescription; 205 @StringRes public final int accessibilityLabel; 206 @StringRes public final int hintText; 207 SecondaryBehavior( @rawableRes int icon, @StringRes int contentDescription, @StringRes int accessibilityLabel, @StringRes int hintText)208 SecondaryBehavior( 209 @DrawableRes int icon, 210 @StringRes int contentDescription, 211 @StringRes int accessibilityLabel, 212 @StringRes int hintText) { 213 this.icon = icon; 214 this.contentDescription = contentDescription; 215 this.accessibilityLabel = accessibilityLabel; 216 this.hintText = hintText; 217 } 218 performAction(AnswerFragment fragment)219 public abstract void performAction(AnswerFragment fragment); 220 applyToView(ImageView view)221 public void applyToView(ImageView view) { 222 view.setImageResource(icon); 223 view.setContentDescription(view.getContext().getText(contentDescription)); 224 } 225 } 226 performSpeakEasy(View unused)227 private void performSpeakEasy(View unused) { 228 answerScreenDelegate.onSpeakEasyCall(); 229 buttonAcceptClicked = true; 230 } 231 performAnswerAndRelease()232 private void performAnswerAndRelease() { 233 restoreAnswerAndReleaseButtonAnimation(); 234 answerScreenDelegate.onAnswerAndReleaseCall(); 235 buttonAcceptClicked = true; 236 } 237 restoreAnswerAndReleaseButtonAnimation()238 private void restoreAnswerAndReleaseButtonAnimation() { 239 answerAndReleaseButton 240 .animate() 241 .alpha(0) 242 .withEndAction( 243 new Runnable() { 244 @Override 245 public void run() { 246 affordanceHolderLayout.reset(false); 247 secondaryButton.animate().alpha(1); 248 } 249 }); 250 } 251 252 private final AccessibilityDelegate accessibilityDelegate = 253 new AccessibilityDelegate() { 254 @Override 255 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 256 super.onInitializeAccessibilityNodeInfo(host, info); 257 if (host == secondaryButton) { 258 CharSequence label = getText(secondaryBehavior.accessibilityLabel); 259 info.addAction(new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, label)); 260 } else if (host == answerAndReleaseButton) { 261 CharSequence label = getText(answerAndReleaseBehavior.accessibilityLabel); 262 info.addAction(new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, label)); 263 } 264 } 265 266 @Override 267 public boolean performAccessibilityAction(View host, int action, Bundle args) { 268 if (action == AccessibilityNodeInfo.ACTION_CLICK) { 269 if (host == secondaryButton) { 270 performSecondaryButtonAction(); 271 return true; 272 } else if (host == answerAndReleaseButton) { 273 performAnswerAndReleaseButtonAction(); 274 return true; 275 } 276 } 277 return super.performAccessibilityAction(host, action, args); 278 } 279 }; 280 281 private final Callback affordanceCallback = 282 new Callback() { 283 @Override 284 public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {} 285 286 @Override 287 public void onAnimationToSideEnded(boolean rightPage) { 288 if (rightPage) { 289 performAnswerAndReleaseButtonAction(); 290 } else { 291 performSecondaryButtonAction(); 292 } 293 } 294 295 @Override 296 public float getMaxTranslationDistance() { 297 View view = getView(); 298 if (view == null) { 299 return 0; 300 } 301 return (float) Math.hypot(view.getWidth(), view.getHeight()); 302 } 303 304 @Override 305 public void onSwipingStarted(boolean rightIcon) {} 306 307 @Override 308 public void onSwipingAborted() {} 309 310 @Override 311 public void onIconClicked(boolean rightIcon) { 312 affordanceHolderLayout.startHintAnimation(rightIcon, null); 313 getAnswerMethod() 314 .setHintText( 315 rightIcon 316 ? getText(answerAndReleaseBehavior.hintText) 317 : getText(secondaryBehavior.hintText)); 318 handler.removeCallbacks(swipeHintRestoreTimer); 319 handler.postDelayed(swipeHintRestoreTimer, HINT_SECONDARY_SHOW_DURATION_MILLIS); 320 } 321 322 @Override 323 public SwipeButtonView getLeftIcon() { 324 return secondaryButton; 325 } 326 327 @Override 328 public SwipeButtonView getRightIcon() { 329 return answerAndReleaseButton; 330 } 331 332 @Override 333 public View getLeftPreview() { 334 return null; 335 } 336 337 @Override 338 public View getRightPreview() { 339 return null; 340 } 341 342 @Override 343 public float getAffordanceFalsingFactor() { 344 return 1.0f; 345 } 346 }; 347 348 private Runnable swipeHintRestoreTimer = this::restoreSwipeHintTexts; 349 performSecondaryButtonAction()350 private void performSecondaryButtonAction() { 351 secondaryBehavior.performAction(this); 352 } 353 performAnswerAndReleaseButtonAction()354 private void performAnswerAndReleaseButtonAction() { 355 answerAndReleaseBehavior.performAction(this); 356 } 357 newInstance( String callId, boolean isRttCall, boolean isVideoCall, boolean isVideoUpgradeRequest, boolean isSelfManagedCamera, boolean allowAnswerAndRelease, boolean hasCallOnHold, boolean allowSpeakEasy)358 public static AnswerFragment newInstance( 359 String callId, 360 boolean isRttCall, 361 boolean isVideoCall, 362 boolean isVideoUpgradeRequest, 363 boolean isSelfManagedCamera, 364 boolean allowAnswerAndRelease, 365 boolean hasCallOnHold, 366 boolean allowSpeakEasy) { 367 Bundle bundle = new Bundle(); 368 bundle.putString(ARG_CALL_ID, Assert.isNotNull(callId)); 369 bundle.putBoolean(ARG_IS_RTT_CALL, isRttCall); 370 bundle.putBoolean(ARG_IS_VIDEO_CALL, isVideoCall); 371 bundle.putBoolean(ARG_IS_VIDEO_UPGRADE_REQUEST, isVideoUpgradeRequest); 372 bundle.putBoolean(ARG_IS_SELF_MANAGED_CAMERA, isSelfManagedCamera); 373 bundle.putBoolean(ARG_ALLOW_ANSWER_AND_RELEASE, allowAnswerAndRelease); 374 bundle.putBoolean(ARG_HAS_CALL_ON_HOLD, hasCallOnHold); 375 bundle.putBoolean(ARG_ALLOW_SPEAK_EASY, allowSpeakEasy); 376 377 AnswerFragment instance = new AnswerFragment(); 378 instance.setArguments(bundle); 379 return instance; 380 } 381 382 @Override isActionTimeout()383 public boolean isActionTimeout() { 384 return (buttonAcceptClicked || buttonRejectClicked) && answerScreenDelegate.isActionTimeout(); 385 } 386 387 @Override 388 @NonNull getCallId()389 public String getCallId() { 390 return Assert.isNotNull(getArguments().getString(ARG_CALL_ID)); 391 } 392 393 @Override isVideoUpgradeRequest()394 public boolean isVideoUpgradeRequest() { 395 return getArguments().getBoolean(ARG_IS_VIDEO_UPGRADE_REQUEST); 396 } 397 398 @Override setTextResponses(List<String> textResponses)399 public void setTextResponses(List<String> textResponses) { 400 if (isVideoCall() || isVideoUpgradeRequest()) { 401 LogUtil.i("AnswerFragment.setTextResponses", "no-op for video calls"); 402 } else if (textResponses == null) { 403 LogUtil.i("AnswerFragment.setTextResponses", "no text responses, hiding secondary button"); 404 this.textResponses = null; 405 secondaryButton.setVisibility(View.INVISIBLE); 406 } else if (getActivity().isInMultiWindowMode()) { 407 LogUtil.i("AnswerFragment.setTextResponses", "in multiwindow, hiding secondary button"); 408 this.textResponses = null; 409 secondaryButton.setVisibility(View.INVISIBLE); 410 } else { 411 LogUtil.i("AnswerFragment.setTextResponses", "textResponses.size: " + textResponses.size()); 412 this.textResponses = new ArrayList<>(textResponses); 413 secondaryButton.setVisibility(View.VISIBLE); 414 } 415 } 416 initSecondaryButton()417 private void initSecondaryButton() { 418 secondaryBehavior = 419 isVideoCall() || isVideoUpgradeRequest() 420 ? SecondaryBehavior.ANSWER_VIDEO_AS_AUDIO 421 : SecondaryBehavior.REJECT_WITH_SMS; 422 secondaryBehavior.applyToView(secondaryButton); 423 424 secondaryButton.setOnClickListener( 425 new OnClickListener() { 426 @Override 427 public void onClick(View v) { 428 performSecondaryButtonAction(); 429 } 430 }); 431 secondaryButton.setClickable(AccessibilityUtil.isAccessibilityEnabled(getContext())); 432 secondaryButton.setFocusable(AccessibilityUtil.isAccessibilityEnabled(getContext())); 433 secondaryButton.setAccessibilityDelegate(accessibilityDelegate); 434 435 if (isVideoUpgradeRequest()) { 436 secondaryButton.setVisibility(View.INVISIBLE); 437 } else if (isVideoCall()) { 438 secondaryButton.setVisibility(View.VISIBLE); 439 } 440 441 answerAndReleaseBehavior = SecondaryBehavior.ANSWER_AND_RELEASE; 442 answerAndReleaseBehavior.applyToView(answerAndReleaseButton); 443 444 answerAndReleaseButton.setClickable(AccessibilityUtil.isAccessibilityEnabled(getContext())); 445 answerAndReleaseButton.setFocusable(AccessibilityUtil.isAccessibilityEnabled(getContext())); 446 answerAndReleaseButton.setAccessibilityDelegate(accessibilityDelegate); 447 448 if (allowAnswerAndRelease()) { 449 answerAndReleaseButton.setVisibility(View.VISIBLE); 450 answerScreenDelegate.onAnswerAndReleaseButtonEnabled(); 451 } else { 452 answerAndReleaseButton.setVisibility(View.INVISIBLE); 453 answerScreenDelegate.onAnswerAndReleaseButtonDisabled(); 454 } 455 answerAndReleaseButton.setOnClickListener( 456 new OnClickListener() { 457 @Override 458 public void onClick(View v) { 459 performAnswerAndReleaseButtonAction(); 460 } 461 }); 462 } 463 464 /** Initialize chip buttons */ initChips()465 private void initChips() { 466 467 if (!allowSpeakEasy()) { 468 chipContainer.setVisibility(View.GONE); 469 return; 470 } 471 chipContainer.setVisibility(View.VISIBLE); 472 473 @SpeakEasyChipResourceId 474 Optional<Integer> chipLayoutOptional = SpeakEasyComponent.get(getContext()).speakEasyChip(); 475 if (chipLayoutOptional.isPresent()) { 476 477 LinearLayout chipLayout = 478 (LinearLayout) getLayoutInflater().inflate(chipLayoutOptional.get(), null); 479 480 chipLayout.setOnClickListener(this::performSpeakEasy); 481 482 chipContainer.addView(chipLayout); 483 } 484 } 485 486 @Override allowAnswerAndRelease()487 public boolean allowAnswerAndRelease() { 488 return getArguments().getBoolean(ARG_ALLOW_ANSWER_AND_RELEASE); 489 } 490 491 @Override allowSpeakEasy()492 public boolean allowSpeakEasy() { 493 return getArguments().getBoolean(ARG_ALLOW_SPEAK_EASY); 494 } 495 hasCallOnHold()496 private boolean hasCallOnHold() { 497 return getArguments().getBoolean(ARG_HAS_CALL_ON_HOLD); 498 } 499 500 @Override hasPendingDialogs()501 public boolean hasPendingDialogs() { 502 boolean hasPendingDialogs = 503 textResponsesFragment != null || createCustomSmsDialogFragment != null; 504 LogUtil.i("AnswerFragment.hasPendingDialogs", "" + hasPendingDialogs); 505 return hasPendingDialogs; 506 } 507 508 @Override dismissPendingDialogs()509 public void dismissPendingDialogs() { 510 LogUtil.i("AnswerFragment.dismissPendingDialogs", null); 511 if (textResponsesFragment != null) { 512 textResponsesFragment.dismiss(); 513 textResponsesFragment = null; 514 } 515 516 if (createCustomSmsDialogFragment != null) { 517 createCustomSmsDialogFragment.dismiss(); 518 createCustomSmsDialogFragment = null; 519 } 520 } 521 522 @Override isShowingLocationUi()523 public boolean isShowingLocationUi() { 524 Fragment fragment = getChildFragmentManager().findFragmentById(R.id.incall_location_holder); 525 return fragment != null && fragment.isVisible(); 526 } 527 528 @Override showLocationUi(@ullable Fragment locationUi)529 public void showLocationUi(@Nullable Fragment locationUi) { 530 boolean isShowing = isShowingLocationUi(); 531 if (!isShowing && locationUi != null) { 532 // Show the location fragment. 533 getChildFragmentManager() 534 .beginTransaction() 535 .replace(R.id.incall_location_holder, locationUi) 536 .commitAllowingStateLoss(); 537 } else if (isShowing && locationUi == null) { 538 // Hide the location fragment 539 Fragment fragment = getChildFragmentManager().findFragmentById(R.id.incall_location_holder); 540 getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss(); 541 } 542 } 543 544 @Override getAnswerScreenFragment()545 public Fragment getAnswerScreenFragment() { 546 return this; 547 } 548 getAnswerMethod()549 private AnswerMethod getAnswerMethod() { 550 return ((AnswerMethod) 551 getChildFragmentManager().findFragmentById(R.id.answer_method_container)); 552 } 553 554 @Override setPrimary(PrimaryInfo primaryInfo)555 public void setPrimary(PrimaryInfo primaryInfo) { 556 LogUtil.i("AnswerFragment.setPrimary", primaryInfo.toString()); 557 this.primaryInfo = primaryInfo; 558 updatePrimaryUI(); 559 updateImportanceBadgeVisibility(); 560 } 561 updatePrimaryUI()562 private void updatePrimaryUI() { 563 if (getView() == null) { 564 return; 565 } 566 contactGridManager.setPrimary(primaryInfo); 567 getAnswerMethod().setShowIncomingWillDisconnect(primaryInfo.answeringDisconnectsOngoingCall()); 568 getAnswerMethod() 569 .setContactPhoto( 570 primaryInfo.photoType() == ContactPhotoType.CONTACT ? primaryInfo.photo() : null); 571 updateDataFragment(); 572 573 if (primaryInfo.shouldShowLocation()) { 574 // Hide the avatar to make room for location 575 contactGridManager.setAvatarHidden(true); 576 } 577 } 578 updateDataFragment()579 private void updateDataFragment() { 580 if (!isAdded()) { 581 return; 582 } 583 LogUtil.enterBlock("AnswerFragment.updateDataFragment"); 584 Fragment current = getChildFragmentManager().findFragmentById(R.id.incall_data_container); 585 Fragment newFragment = null; 586 587 MultimediaData multimediaData = getSessionData(); 588 if (multimediaData != null 589 && (!TextUtils.isEmpty(multimediaData.getText()) 590 || (multimediaData.getImageUri() != null) 591 || (multimediaData.getLocation() != null && canShowMap()))) { 592 // Need message fragment 593 String subject = multimediaData.getText(); 594 Uri imageUri = multimediaData.getImageUri(); 595 Location location = multimediaData.getLocation(); 596 if (!(current instanceof MultimediaFragment) 597 || !Objects.equals(((MultimediaFragment) current).getSubject(), subject) 598 || !Objects.equals(((MultimediaFragment) current).getImageUri(), imageUri) 599 || !Objects.equals(((MultimediaFragment) current).getLocation(), location)) { 600 LogUtil.i("AnswerFragment.updateDataFragment", "Replacing multimedia fragment"); 601 // Needs replacement 602 newFragment = 603 MultimediaFragment.newInstance( 604 multimediaData, 605 false /* isInteractive */, 606 !primaryInfo.isSpam() /* showAvatar */, 607 primaryInfo.isSpam()); 608 } 609 } else if (shouldShowAvatar()) { 610 // Needs Avatar 611 if (!(current instanceof AvatarFragment)) { 612 LogUtil.i("AnswerFragment.updateDataFragment", "Replacing avatar fragment"); 613 // Needs replacement 614 newFragment = new AvatarFragment(); 615 } 616 } else { 617 // Needs empty 618 if (current != null) { 619 LogUtil.i("AnswerFragment.updateDataFragment", "Removing current fragment"); 620 getChildFragmentManager().beginTransaction().remove(current).commitNow(); 621 } 622 contactGridManager.setAvatarImageView(null, 0, false); 623 } 624 625 if (newFragment != null) { 626 getChildFragmentManager() 627 .beginTransaction() 628 .replace(R.id.incall_data_container, newFragment) 629 .commitNow(); 630 } 631 } 632 shouldShowAvatar()633 private boolean shouldShowAvatar() { 634 return !isVideoCall() && !isVideoUpgradeRequest(); 635 } 636 canShowMap()637 private boolean canShowMap() { 638 return MapsComponent.get(getContext()).getMaps().isAvailable(); 639 } 640 641 @Override updateAvatar(AvatarPresenter avatarContainer)642 public void updateAvatar(AvatarPresenter avatarContainer) { 643 contactGridManager.setAvatarImageView( 644 avatarContainer.getAvatarImageView(), 645 avatarContainer.getAvatarSize(), 646 avatarContainer.shouldShowAnonymousAvatar()); 647 } 648 649 @Override setSecondary(@onNull SecondaryInfo secondaryInfo)650 public void setSecondary(@NonNull SecondaryInfo secondaryInfo) {} 651 652 @Override setCallState(@onNull PrimaryCallState primaryCallState)653 public void setCallState(@NonNull PrimaryCallState primaryCallState) { 654 LogUtil.i("AnswerFragment.setCallState", primaryCallState.toString()); 655 this.primaryCallState = primaryCallState; 656 contactGridManager.setCallState(primaryCallState); 657 } 658 659 @Override setEndCallButtonEnabled(boolean enabled, boolean animate)660 public void setEndCallButtonEnabled(boolean enabled, boolean animate) {} 661 662 @Override showManageConferenceCallButton(boolean visible)663 public void showManageConferenceCallButton(boolean visible) {} 664 665 @Override isManageConferenceVisible()666 public boolean isManageConferenceVisible() { 667 return false; 668 } 669 670 @Override dispatchPopulateAccessibilityEvent(AccessibilityEvent event)671 public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 672 contactGridManager.dispatchPopulateAccessibilityEvent(event); 673 // Add prompt of how to accept/decline call with swipe gesture. 674 if (AccessibilityUtil.isTouchExplorationEnabled(getContext())) { 675 event 676 .getText() 677 .add(getResources().getString(R.string.a11y_incoming_call_swipe_gesture_prompt)); 678 } 679 } 680 681 @Override showNoteSentToast()682 public void showNoteSentToast() {} 683 684 @Override updateInCallScreenColors()685 public void updateInCallScreenColors() {} 686 687 @Override onInCallScreenDialpadVisibilityChange(boolean isShowing)688 public void onInCallScreenDialpadVisibilityChange(boolean isShowing) {} 689 690 @Override getAnswerAndDialpadContainerResourceId()691 public int getAnswerAndDialpadContainerResourceId() { 692 throw Assert.createUnsupportedOperationFailException(); 693 } 694 695 @Override getInCallScreenFragment()696 public Fragment getInCallScreenFragment() { 697 return this; 698 } 699 700 @Override onDestroy()701 public void onDestroy() { 702 super.onDestroy(); 703 } 704 705 @Override onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)706 public View onCreateView( 707 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 708 Trace.beginSection("AnswerFragment.onCreateView"); 709 Bundle arguments = getArguments(); 710 Assert.checkState(arguments.containsKey(ARG_CALL_ID)); 711 Assert.checkState(arguments.containsKey(ARG_IS_RTT_CALL)); 712 Assert.checkState(arguments.containsKey(ARG_IS_VIDEO_CALL)); 713 Assert.checkState(arguments.containsKey(ARG_IS_VIDEO_UPGRADE_REQUEST)); 714 715 buttonAcceptClicked = false; 716 buttonRejectClicked = false; 717 718 View view = inflater.inflate(R.layout.fragment_incoming_call, container, false); 719 secondaryButton = (SwipeButtonView) view.findViewById(R.id.incoming_secondary_button); 720 answerAndReleaseButton = (SwipeButtonView) view.findViewById(R.id.incoming_secondary_button2); 721 722 affordanceHolderLayout = (AffordanceHolderLayout) view.findViewById(R.id.incoming_container); 723 affordanceHolderLayout.setAffordanceCallback(affordanceCallback); 724 725 chipContainer = view.findViewById(R.id.incall_data_container_chip_container); 726 727 importanceBadge = view.findViewById(R.id.incall_important_call_badge); 728 importanceBadge 729 .getViewTreeObserver() 730 .addOnGlobalLayoutListener( 731 new OnGlobalLayoutListener() { 732 @Override 733 public void onGlobalLayout() { 734 int leftRightPadding = importanceBadge.getHeight() / 2; 735 importanceBadge.setPadding( 736 leftRightPadding, 737 importanceBadge.getPaddingTop(), 738 leftRightPadding, 739 importanceBadge.getPaddingBottom()); 740 } 741 }); 742 updateImportanceBadgeVisibility(); 743 744 contactGridManager = new ContactGridManager(view, null, 0, false /* showAnonymousAvatar */); 745 boolean isInMultiWindowMode = getActivity().isInMultiWindowMode(); 746 contactGridManager.onMultiWindowModeChanged(isInMultiWindowMode); 747 748 Fragment answerMethod = 749 getChildFragmentManager().findFragmentById(R.id.answer_method_container); 750 if (AnswerMethodFactory.needsReplacement(answerMethod)) { 751 getChildFragmentManager() 752 .beginTransaction() 753 .replace( 754 R.id.answer_method_container, AnswerMethodFactory.createAnswerMethod(getActivity())) 755 .commitNow(); 756 } 757 758 answerScreenDelegate = 759 FragmentUtils.getParentUnsafe(this, AnswerScreenDelegateFactory.class) 760 .newAnswerScreenDelegate(this); 761 762 initSecondaryButton(); 763 initChips(); 764 765 int flags = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; 766 if (!isInMultiWindowMode 767 && (getActivity().checkSelfPermission(permission.STATUS_BAR) 768 == PackageManager.PERMISSION_GRANTED)) { 769 LogUtil.i("AnswerFragment.onCreateView", "STATUS_BAR permission granted, disabling nav bar"); 770 // These flags will suppress the alert that the activity is in full view mode 771 // during an incoming call on a fresh system/factory reset of the app 772 flags |= STATUS_BAR_DISABLE_BACK | STATUS_BAR_DISABLE_HOME | STATUS_BAR_DISABLE_RECENT; 773 } 774 view.setSystemUiVisibility(flags); 775 if (isVideoCall() || isVideoUpgradeRequest()) { 776 if (VideoUtils.hasCameraPermissionAndShownPrivacyToast(getContext())) { 777 if (isSelfManagedCamera()) { 778 answerVideoCallScreen = new SelfManagedAnswerVideoCallScreen(getCallId(), this, view); 779 } else { 780 answerVideoCallScreen = new AnswerVideoCallScreen(getCallId(), this, view); 781 } 782 } else { 783 view.findViewById(R.id.videocall_video_off).setVisibility(View.VISIBLE); 784 } 785 } 786 787 Trace.endSection(); 788 return view; 789 } 790 791 @Override onAttach(Context context)792 public void onAttach(Context context) { 793 super.onAttach(context); 794 FragmentUtils.checkParent(this, InCallScreenDelegateFactory.class); 795 } 796 797 @Override onViewCreated(final View view, @Nullable Bundle savedInstanceState)798 public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) { 799 Trace.beginSection("AnswerFragment.onViewCreated"); 800 super.onViewCreated(view, savedInstanceState); 801 createInCallScreenDelegate(); 802 updateUI(); 803 804 if (savedInstanceState == null || !savedInstanceState.getBoolean(STATE_HAS_ANIMATED_ENTRY)) { 805 ViewUtil.doOnGlobalLayout(view, this::animateEntry); 806 } 807 Trace.endSection(); 808 } 809 810 @Override onResume()811 public void onResume() { 812 Trace.beginSection("AnswerFragment.onResume"); 813 super.onResume(); 814 LogUtil.i("AnswerFragment.onResume", null); 815 restoreSwipeHintTexts(); 816 inCallScreenDelegate.onInCallScreenResumed(); 817 Trace.endSection(); 818 } 819 820 @Override onStart()821 public void onStart() { 822 Trace.beginSection("AnswerFragment.onStart"); 823 super.onStart(); 824 LogUtil.i("AnswerFragment.onStart", null); 825 826 updateUI(); 827 if (answerVideoCallScreen != null) { 828 answerVideoCallScreen.onVideoScreenStart(); 829 } 830 Trace.endSection(); 831 } 832 833 @Override onStop()834 public void onStop() { 835 Trace.beginSection("AnswerFragment.onStop"); 836 super.onStop(); 837 LogUtil.i("AnswerFragment.onStop", null); 838 839 handler.removeCallbacks(swipeHintRestoreTimer); 840 if (answerVideoCallScreen != null) { 841 answerVideoCallScreen.onVideoScreenStop(); 842 } 843 Trace.endSection(); 844 } 845 846 @Override onPause()847 public void onPause() { 848 Trace.beginSection("AnswerFragment.onPause"); 849 super.onPause(); 850 LogUtil.i("AnswerFragment.onPause", null); 851 inCallScreenDelegate.onInCallScreenPaused(); 852 Trace.endSection(); 853 } 854 855 @Override onDestroyView()856 public void onDestroyView() { 857 LogUtil.i("AnswerFragment.onDestroyView", null); 858 if (answerVideoCallScreen != null) { 859 answerVideoCallScreen = null; 860 } 861 super.onDestroyView(); 862 inCallScreenDelegate.onInCallScreenUnready(); 863 answerScreenDelegate.onAnswerScreenUnready(); 864 } 865 866 @Override onSaveInstanceState(Bundle bundle)867 public void onSaveInstanceState(Bundle bundle) { 868 super.onSaveInstanceState(bundle); 869 bundle.putBoolean(STATE_HAS_ANIMATED_ENTRY, hasAnimatedEntry); 870 } 871 updateUI()872 private void updateUI() { 873 if (getView() == null) { 874 return; 875 } 876 877 if (primaryInfo != null) { 878 updatePrimaryUI(); 879 } 880 if (primaryCallState != null) { 881 contactGridManager.setCallState(primaryCallState); 882 } 883 884 restoreBackgroundMaskColor(); 885 } 886 887 @Override isRttCall()888 public boolean isRttCall() { 889 return getArguments().getBoolean(ARG_IS_RTT_CALL); 890 } 891 892 @Override isVideoCall()893 public boolean isVideoCall() { 894 return getArguments().getBoolean(ARG_IS_VIDEO_CALL); 895 } 896 isSelfManagedCamera()897 public boolean isSelfManagedCamera() { 898 return getArguments().getBoolean(ARG_IS_SELF_MANAGED_CAMERA); 899 } 900 901 @Override onAnswerProgressUpdate(@loatRangefrom = -1f, to = 1f) float answerProgress)902 public void onAnswerProgressUpdate(@FloatRange(from = -1f, to = 1f) float answerProgress) { 903 // Don't fade the window background for call waiting or video upgrades. Fading the background 904 // shows the system wallpaper which looks bad because on reject we switch to another call. 905 if (primaryCallState.state() == DialerCallState.INCOMING && !isVideoCall()) { 906 answerScreenDelegate.updateWindowBackgroundColor(answerProgress); 907 } 908 909 // Fade and scale contact name and video call text 910 float startDelay = .25f; 911 // Header progress is zero over positiveAdjustedProgress = [0, startDelay], 912 // linearly increases over (startDelay, 1] until reaching 1 when positiveAdjustedProgress = 1 913 float headerProgress = Math.max(0, (Math.abs(answerProgress) - 1) / (1 - startDelay) + 1); 914 fadeToward(contactGridManager.getContainerView(), 1 - headerProgress); 915 scaleToward(contactGridManager.getContainerView(), MathUtil.lerp(1f, .75f, headerProgress)); 916 917 if (Math.abs(answerProgress) >= .0001) { 918 affordanceHolderLayout.animateHideLeftRightIcon(); 919 handler.removeCallbacks(swipeHintRestoreTimer); 920 restoreSwipeHintTexts(); 921 } 922 } 923 924 @Override answerFromMethod()925 public void answerFromMethod() { 926 acceptCallByUser(false /* answerVideoAsAudio */); 927 } 928 929 @Override rejectFromMethod()930 public void rejectFromMethod() { 931 rejectCall(); 932 } 933 934 @Override resetAnswerProgress()935 public void resetAnswerProgress() { 936 affordanceHolderLayout.reset(true); 937 restoreBackgroundMaskColor(); 938 } 939 animateEntry(@onNull View rootView)940 private void animateEntry(@NonNull View rootView) { 941 if (!isAdded()) { 942 LogUtil.i( 943 "AnswerFragment.animateEntry", 944 "Not currently added to Activity. Will not start entry animation."); 945 return; 946 } 947 contactGridManager.getContainerView().setAlpha(0f); 948 Animator alpha = 949 ObjectAnimator.ofFloat(contactGridManager.getContainerView(), View.ALPHA, 0, 1); 950 Animator topRow = createTranslation(rootView.findViewById(R.id.contactgrid_top_row)); 951 Animator contactName = createTranslation(rootView.findViewById(R.id.contactgrid_contact_name)); 952 Animator bottomRow = createTranslation(rootView.findViewById(R.id.contactgrid_bottom_row)); 953 Animator important = createTranslation(importanceBadge); 954 Animator dataContainer = createTranslation(rootView.findViewById(R.id.incall_data_container)); 955 956 AnimatorSet animatorSet = new AnimatorSet(); 957 AnimatorSet.Builder builder = animatorSet.play(alpha); 958 builder.with(topRow).with(contactName).with(bottomRow).with(important).with(dataContainer); 959 if (isShowingLocationUi()) { 960 builder.with(createTranslation(rootView.findViewById(R.id.incall_location_holder))); 961 } 962 animatorSet.setDuration( 963 rootView.getResources().getInteger(R.integer.answer_animate_entry_millis)); 964 animatorSet.addListener( 965 new AnimatorListenerAdapter() { 966 @Override 967 public void onAnimationEnd(Animator animation) { 968 hasAnimatedEntry = true; 969 } 970 }); 971 animatorSet.start(); 972 } 973 createTranslation(View view)974 private ObjectAnimator createTranslation(View view) { 975 float translationY = view.getTop() * 0.5f; 976 ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, translationY, 0); 977 animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 978 return animator; 979 } 980 acceptCallByUser(boolean answerVideoAsAudio)981 private void acceptCallByUser(boolean answerVideoAsAudio) { 982 LogUtil.i("AnswerFragment.acceptCallByUser", answerVideoAsAudio ? " answerVideoAsAudio" : ""); 983 if (!buttonAcceptClicked) { 984 answerScreenDelegate.onAnswer(answerVideoAsAudio); 985 buttonAcceptClicked = true; 986 } 987 } 988 rejectCall()989 private void rejectCall() { 990 LogUtil.i("AnswerFragment.rejectCall", null); 991 if (!buttonRejectClicked) { 992 Context context = getContext(); 993 if (context == null) { 994 LogUtil.w( 995 "AnswerFragment.rejectCall", 996 "Null context when rejecting call. Logger call was skipped"); 997 } else { 998 Logger.get(context) 999 .logImpression(DialerImpression.Type.REJECT_INCOMING_CALL_FROM_ANSWER_SCREEN); 1000 } 1001 buttonRejectClicked = true; 1002 answerScreenDelegate.onReject(); 1003 } 1004 } 1005 restoreBackgroundMaskColor()1006 private void restoreBackgroundMaskColor() { 1007 answerScreenDelegate.updateWindowBackgroundColor(0); 1008 } 1009 restoreSwipeHintTexts()1010 private void restoreSwipeHintTexts() { 1011 if (getAnswerMethod() != null) { 1012 if (allowAnswerAndRelease()) { 1013 if (hasCallOnHold()) { 1014 getAnswerMethod() 1015 .setHintText(getText(R.string.call_incoming_default_label_answer_and_release_third)); 1016 } else if (primaryCallState.supportsCallOnHold()) { 1017 getAnswerMethod() 1018 .setHintText(getText(R.string.call_incoming_default_label_answer_and_release_second)); 1019 } 1020 } else { 1021 getAnswerMethod().setHintText(null); 1022 } 1023 } 1024 } 1025 showMessageMenu()1026 private void showMessageMenu() { 1027 LogUtil.i("AnswerFragment.showMessageMenu", "Show sms menu."); 1028 if (getContext() == null || isDetached() || getChildFragmentManager().isDestroyed()) { 1029 return; 1030 } 1031 1032 textResponsesFragment = SmsBottomSheetFragment.newInstance(textResponses); 1033 textResponsesFragment.show(getChildFragmentManager(), null); 1034 secondaryButton 1035 .animate() 1036 .alpha(0) 1037 .withEndAction( 1038 new Runnable() { 1039 @Override 1040 public void run() { 1041 affordanceHolderLayout.reset(false); 1042 secondaryButton.animate().alpha(1); 1043 } 1044 }); 1045 1046 TelecomUtil.silenceRinger(getContext()); 1047 } 1048 1049 @Override acquireInCallUiLock(String tag)1050 public InCallUiLock acquireInCallUiLock(String tag) { 1051 return answerScreenDelegate.acquireInCallUiLock(tag); 1052 } 1053 1054 @Override smsSelected(@ullable CharSequence text)1055 public void smsSelected(@Nullable CharSequence text) { 1056 LogUtil.i("AnswerFragment.smsSelected", null); 1057 textResponsesFragment = null; 1058 1059 if (text == null) { 1060 createCustomSmsDialogFragment = CreateCustomSmsDialogFragment.newInstance(); 1061 createCustomSmsDialogFragment.show(getChildFragmentManager(), null); 1062 return; 1063 } 1064 1065 if (primaryCallState != null && canRejectCallWithSms()) { 1066 rejectCall(); 1067 answerScreenDelegate.onRejectCallWithMessage(text.toString()); 1068 } 1069 } 1070 1071 @Override smsDismissed()1072 public void smsDismissed() { 1073 LogUtil.i("AnswerFragment.smsDismissed", null); 1074 textResponsesFragment = null; 1075 } 1076 1077 @Override customSmsCreated(@onNull CharSequence text)1078 public void customSmsCreated(@NonNull CharSequence text) { 1079 LogUtil.i("AnswerFragment.customSmsCreated", null); 1080 createCustomSmsDialogFragment = null; 1081 if (primaryCallState != null && canRejectCallWithSms()) { 1082 rejectCall(); 1083 answerScreenDelegate.onRejectCallWithMessage(text.toString()); 1084 } 1085 } 1086 1087 @Override customSmsDismissed()1088 public void customSmsDismissed() { 1089 LogUtil.i("AnswerFragment.customSmsDismissed", null); 1090 createCustomSmsDialogFragment = null; 1091 } 1092 canRejectCallWithSms()1093 private boolean canRejectCallWithSms() { 1094 return primaryCallState != null 1095 && !(primaryCallState.state() == DialerCallState.DISCONNECTED 1096 || primaryCallState.state() == DialerCallState.DISCONNECTING 1097 || primaryCallState.state() == DialerCallState.IDLE); 1098 } 1099 createInCallScreenDelegate()1100 private void createInCallScreenDelegate() { 1101 inCallScreenDelegate = 1102 FragmentUtils.getParentUnsafe(this, InCallScreenDelegateFactory.class) 1103 .newInCallScreenDelegate(); 1104 Assert.isNotNull(inCallScreenDelegate); 1105 inCallScreenDelegate.onInCallScreenDelegateInit(this); 1106 inCallScreenDelegate.onInCallScreenReady(); 1107 } 1108 updateImportanceBadgeVisibility()1109 private void updateImportanceBadgeVisibility() { 1110 if (!isAdded() || getView() == null) { 1111 return; 1112 } 1113 1114 if (!getResources().getBoolean(R.bool.answer_important_call_allowed) || primaryInfo.isSpam()) { 1115 importanceBadge.setVisibility(View.GONE); 1116 return; 1117 } 1118 1119 MultimediaData multimediaData = getSessionData(); 1120 boolean showImportant = multimediaData != null && multimediaData.isImportant(); 1121 TransitionManager.beginDelayedTransition((ViewGroup) importanceBadge.getParent()); 1122 // TODO (keyboardr): Change this back to being View.INVISIBLE once mocks are available to 1123 // properly handle smaller screens 1124 importanceBadge.setVisibility(showImportant ? View.VISIBLE : View.GONE); 1125 } 1126 1127 @Nullable getSessionData()1128 private MultimediaData getSessionData() { 1129 if (primaryInfo == null) { 1130 return null; 1131 } 1132 if (isVideoUpgradeRequest()) { 1133 return null; 1134 } 1135 return primaryInfo.multimediaData(); 1136 } 1137 1138 /** Shows the Avatar image if available. */ 1139 public static class AvatarFragment extends Fragment implements AvatarPresenter { 1140 1141 private ImageView avatarImageView; 1142 1143 @Nullable 1144 @Override onCreateView( LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle)1145 public View onCreateView( 1146 LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) { 1147 return layoutInflater.inflate(R.layout.fragment_avatar, viewGroup, false); 1148 } 1149 1150 @Override onViewCreated(View view, @Nullable Bundle bundle)1151 public void onViewCreated(View view, @Nullable Bundle bundle) { 1152 super.onViewCreated(view, bundle); 1153 avatarImageView = ((ImageView) view.findViewById(R.id.contactgrid_avatar)); 1154 FragmentUtils.getParentUnsafe(this, MultimediaFragment.Holder.class).updateAvatar(this); 1155 } 1156 1157 @NonNull 1158 @Override getAvatarImageView()1159 public ImageView getAvatarImageView() { 1160 return avatarImageView; 1161 } 1162 1163 @Override getAvatarSize()1164 public int getAvatarSize() { 1165 return getResources().getDimensionPixelSize(R.dimen.answer_avatar_size); 1166 } 1167 1168 @Override shouldShowAnonymousAvatar()1169 public boolean shouldShowAnonymousAvatar() { 1170 return false; 1171 } 1172 } 1173 } 1174