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