• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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