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