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