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.incall.impl;
18 
19 import android.Manifest.permission;
20 import android.annotation.SuppressLint;
21 import android.app.Activity;
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import android.support.annotation.ColorInt;
27 import android.support.annotation.NonNull;
28 import android.support.annotation.Nullable;
29 import android.support.v4.app.Fragment;
30 import android.support.v4.app.FragmentTransaction;
31 import android.support.v4.content.ContextCompat;
32 import android.telecom.CallAudioState;
33 import android.telephony.TelephonyManager;
34 import android.transition.TransitionManager;
35 import android.view.LayoutInflater;
36 import android.view.View;
37 import android.view.View.OnAttachStateChangeListener;
38 import android.view.View.OnClickListener;
39 import android.view.ViewGroup;
40 import android.view.Window;
41 import android.view.accessibility.AccessibilityEvent;
42 import android.widget.ImageView;
43 import android.widget.RelativeLayout;
44 import android.widget.Toast;
45 import com.android.dialer.common.Assert;
46 import com.android.dialer.common.FragmentUtils;
47 import com.android.dialer.common.LogUtil;
48 import com.android.dialer.logging.DialerImpression;
49 import com.android.dialer.logging.Logger;
50 import com.android.dialer.multimedia.MultimediaData;
51 import com.android.dialer.strictmode.StrictModeUtils;
52 import com.android.dialer.widget.LockableViewPager;
53 import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment;
54 import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment.AudioRouteSelectorPresenter;
55 import com.android.incallui.contactgrid.ContactGridManager;
56 import com.android.incallui.hold.OnHoldFragment;
57 import com.android.incallui.incall.impl.ButtonController.SpeakerButtonController;
58 import com.android.incallui.incall.impl.ButtonController.UpgradeToRttButtonController;
59 import com.android.incallui.incall.impl.InCallButtonGridFragment.OnButtonGridCreatedListener;
60 import com.android.incallui.incall.protocol.InCallButtonIds;
61 import com.android.incallui.incall.protocol.InCallButtonIdsExtension;
62 import com.android.incallui.incall.protocol.InCallButtonUi;
63 import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
64 import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
65 import com.android.incallui.incall.protocol.InCallScreen;
66 import com.android.incallui.incall.protocol.InCallScreenDelegate;
67 import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
68 import com.android.incallui.incall.protocol.PrimaryCallState;
69 import com.android.incallui.incall.protocol.PrimaryCallState.ButtonState;
70 import com.android.incallui.incall.protocol.PrimaryInfo;
71 import com.android.incallui.incall.protocol.SecondaryInfo;
72 import java.util.ArrayList;
73 import java.util.List;
74 
75 /** Fragment that shows UI for an ongoing voice call. */
76 public class InCallFragment extends Fragment
77     implements InCallScreen,
78         InCallButtonUi,
79         OnClickListener,
80         AudioRouteSelectorPresenter,
81         OnButtonGridCreatedListener {
82 
83   private List<ButtonController> buttonControllers = new ArrayList<>();
84   private View endCallButton;
85   private InCallPaginator paginator;
86   private LockableViewPager pager;
87   private InCallPagerAdapter adapter;
88   private ContactGridManager contactGridManager;
89   private InCallScreenDelegate inCallScreenDelegate;
90   private InCallButtonUiDelegate inCallButtonUiDelegate;
91   private InCallButtonGridFragment inCallButtonGridFragment;
92   @Nullable private ButtonChooser buttonChooser;
93   private SecondaryInfo savedSecondaryInfo;
94   private int voiceNetworkType;
95   private int phoneType;
96   private boolean stateRestored;
97 
98   // Add animation to educate users. If a call has enriched calling attachments then we'll
99   // initially show the attachment page. After a delay seconds we'll animate to the button grid.
100   private final Handler handler = new Handler();
101   private final Runnable pagerRunnable =
102       new Runnable() {
103         @Override
104         public void run() {
105           pager.setCurrentItem(adapter.getButtonGridPosition());
106         }
107       };
108 
isSupportedButton(@nCallButtonIds int id)109   private static boolean isSupportedButton(@InCallButtonIds int id) {
110     return id == InCallButtonIds.BUTTON_AUDIO
111         || id == InCallButtonIds.BUTTON_MUTE
112         || id == InCallButtonIds.BUTTON_DIALPAD
113         || id == InCallButtonIds.BUTTON_HOLD
114         || id == InCallButtonIds.BUTTON_SWAP
115         || id == InCallButtonIds.BUTTON_UPGRADE_TO_VIDEO
116         || id == InCallButtonIds.BUTTON_ADD_CALL
117         || id == InCallButtonIds.BUTTON_MERGE
118         || id == InCallButtonIds.BUTTON_MANAGE_VOICE_CONFERENCE
119         || id == InCallButtonIds.BUTTON_SWAP_SIM
120         || id == InCallButtonIds.BUTTON_UPGRADE_TO_RTT;
121   }
122 
123   @Override
onAttach(Context context)124   public void onAttach(Context context) {
125     super.onAttach(context);
126     if (savedSecondaryInfo != null) {
127       setSecondary(savedSecondaryInfo);
128     }
129   }
130 
131   @Override
onCreate(Bundle savedInstanceState)132   public void onCreate(Bundle savedInstanceState) {
133     super.onCreate(savedInstanceState);
134     inCallButtonUiDelegate =
135         FragmentUtils.getParent(this, InCallButtonUiDelegateFactory.class)
136             .newInCallButtonUiDelegate();
137     if (savedInstanceState != null) {
138       inCallButtonUiDelegate.onRestoreInstanceState(savedInstanceState);
139       stateRestored = true;
140     }
141   }
142 
143   @Nullable
144   @Override
145   @SuppressLint("MissingPermission")
onCreateView( @onNull LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle)146   public View onCreateView(
147       @NonNull LayoutInflater layoutInflater,
148       @Nullable ViewGroup viewGroup,
149       @Nullable Bundle bundle) {
150     LogUtil.i("InCallFragment.onCreateView", null);
151     getActivity().setTheme(R.style.Theme_InCallScreen);
152     // Bypass to avoid StrictModeResourceMismatchViolation
153     final View view =
154         StrictModeUtils.bypass(
155             () -> layoutInflater.inflate(R.layout.frag_incall_voice, viewGroup, false));
156     contactGridManager =
157         new ContactGridManager(
158             view,
159             (ImageView) view.findViewById(R.id.contactgrid_avatar),
160             getResources().getDimensionPixelSize(R.dimen.incall_avatar_size),
161             true /* showAnonymousAvatar */);
162     contactGridManager.onMultiWindowModeChanged(getActivity().isInMultiWindowMode());
163 
164     paginator = (InCallPaginator) view.findViewById(R.id.incall_paginator);
165     pager = (LockableViewPager) view.findViewById(R.id.incall_pager);
166     pager.setOnTouchListener(
167         (v, event) -> {
168           handler.removeCallbacks(pagerRunnable);
169           return false;
170         });
171 
172     endCallButton = view.findViewById(R.id.incall_end_call);
173     endCallButton.setOnClickListener(this);
174 
175     if (ContextCompat.checkSelfPermission(getContext(), permission.READ_PHONE_STATE)
176         != PackageManager.PERMISSION_GRANTED) {
177       voiceNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
178     } else {
179       voiceNetworkType =
180           getContext().getSystemService(TelephonyManager.class).getVoiceNetworkType();
181     }
182     // TODO(a bug): Change to use corresponding phone type used for current call.
183     phoneType = getContext().getSystemService(TelephonyManager.class).getPhoneType();
184 
185     // Workaround to adjust padding for status bar and navigation bar since fitsSystemWindows
186     // doesn't work well when switching with other fragments.
187     view.addOnAttachStateChangeListener(
188         new OnAttachStateChangeListener() {
189           @Override
190           public void onViewAttachedToWindow(View v) {
191             View container = v.findViewById(R.id.incall_ui_container);
192             int topInset = v.getRootWindowInsets().getSystemWindowInsetTop();
193             int bottomInset = v.getRootWindowInsets().getSystemWindowInsetBottom();
194             if (topInset != container.getPaddingTop()) {
195               TransitionManager.beginDelayedTransition(((ViewGroup) container.getParent()));
196               container.setPadding(0, topInset, 0, bottomInset);
197             }
198           }
199 
200           @Override
201           public void onViewDetachedFromWindow(View v) {}
202         });
203     return view;
204   }
205 
206   @Override
onResume()207   public void onResume() {
208     super.onResume();
209     inCallScreenDelegate.onInCallScreenResumed();
210   }
211 
212   @Override
onViewCreated(@onNull View view, @Nullable Bundle bundle)213   public void onViewCreated(@NonNull View view, @Nullable Bundle bundle) {
214     LogUtil.i("InCallFragment.onViewCreated", null);
215     super.onViewCreated(view, bundle);
216     inCallScreenDelegate =
217         FragmentUtils.getParent(this, InCallScreenDelegateFactory.class).newInCallScreenDelegate();
218     Assert.isNotNull(inCallScreenDelegate);
219 
220     buttonControllers.add(new ButtonController.MuteButtonController(inCallButtonUiDelegate));
221     buttonControllers.add(new ButtonController.SpeakerButtonController(inCallButtonUiDelegate));
222     buttonControllers.add(new ButtonController.DialpadButtonController(inCallButtonUiDelegate));
223     buttonControllers.add(new ButtonController.HoldButtonController(inCallButtonUiDelegate));
224     buttonControllers.add(new ButtonController.AddCallButtonController(inCallButtonUiDelegate));
225     buttonControllers.add(new ButtonController.SwapButtonController(inCallButtonUiDelegate));
226     buttonControllers.add(new ButtonController.MergeButtonController(inCallButtonUiDelegate));
227     buttonControllers.add(new ButtonController.SwapSimButtonController(inCallButtonUiDelegate));
228     buttonControllers.add(
229         new ButtonController.UpgradeToVideoButtonController(inCallButtonUiDelegate));
230     buttonControllers.add(new UpgradeToRttButtonController(inCallButtonUiDelegate));
231     buttonControllers.add(
232         new ButtonController.ManageConferenceButtonController(inCallScreenDelegate));
233     buttonControllers.add(
234         new ButtonController.SwitchToSecondaryButtonController(inCallScreenDelegate));
235 
236     inCallScreenDelegate.onInCallScreenDelegateInit(this);
237     inCallScreenDelegate.onInCallScreenReady();
238   }
239 
240   @Override
onPause()241   public void onPause() {
242     super.onPause();
243     inCallScreenDelegate.onInCallScreenPaused();
244   }
245 
246   @Override
onDestroyView()247   public void onDestroyView() {
248     super.onDestroyView();
249     inCallScreenDelegate.onInCallScreenUnready();
250   }
251 
252   @Override
onSaveInstanceState(Bundle outState)253   public void onSaveInstanceState(Bundle outState) {
254     super.onSaveInstanceState(outState);
255     inCallButtonUiDelegate.onSaveInstanceState(outState);
256   }
257 
258   @Override
onClick(View view)259   public void onClick(View view) {
260     if (view == endCallButton) {
261       LogUtil.i("InCallFragment.onClick", "end call button clicked");
262       Logger.get(getContext())
263           .logImpression(DialerImpression.Type.IN_CALL_DIALPAD_HANG_UP_BUTTON_PRESSED);
264       inCallScreenDelegate.onEndCallClicked();
265     } else {
266       LogUtil.e("InCallFragment.onClick", "unknown view: " + view);
267       Assert.fail();
268     }
269   }
270 
271   @Override
setPrimary(@onNull PrimaryInfo primaryInfo)272   public void setPrimary(@NonNull PrimaryInfo primaryInfo) {
273     LogUtil.i("InCallFragment.setPrimary", primaryInfo.toString());
274     setAdapterMedia(primaryInfo.multimediaData(), primaryInfo.showInCallButtonGrid());
275     contactGridManager.setPrimary(primaryInfo);
276 
277     if (primaryInfo.shouldShowLocation()) {
278       // Hide the avatar to make room for location
279       contactGridManager.setAvatarHidden(true);
280 
281       // Need to let the dialpad move up a little further when location info is being shown
282       View dialpadView = getView().findViewById(R.id.incall_dialpad_container);
283       ViewGroup.LayoutParams params = dialpadView.getLayoutParams();
284       if (params instanceof RelativeLayout.LayoutParams) {
285         ((RelativeLayout.LayoutParams) params).removeRule(RelativeLayout.BELOW);
286       }
287       dialpadView.setLayoutParams(params);
288     }
289   }
290 
setAdapterMedia(MultimediaData multimediaData, boolean showInCallButtonGrid)291   private void setAdapterMedia(MultimediaData multimediaData, boolean showInCallButtonGrid) {
292     if (adapter == null) {
293       adapter =
294           new InCallPagerAdapter(getChildFragmentManager(), multimediaData, showInCallButtonGrid);
295       pager.setAdapter(adapter);
296     } else {
297       adapter.setAttachments(multimediaData);
298     }
299 
300     if (adapter.getCount() > 1 && getResources().getInteger(R.integer.incall_num_rows) > 1) {
301       paginator.setVisibility(View.VISIBLE);
302       paginator.setupWithViewPager(pager);
303       pager.setSwipingLocked(false);
304       if (!stateRestored) {
305         handler.postDelayed(pagerRunnable, 4_000);
306       } else {
307         pager.setCurrentItem(adapter.getButtonGridPosition(), false /* animateScroll */);
308       }
309     } else {
310       paginator.setVisibility(View.GONE);
311     }
312   }
313 
314   @Override
setSecondary(@onNull SecondaryInfo secondaryInfo)315   public void setSecondary(@NonNull SecondaryInfo secondaryInfo) {
316     LogUtil.i("InCallFragment.setSecondary", secondaryInfo.toString());
317     updateButtonStates();
318 
319     if (!isAdded()) {
320       savedSecondaryInfo = secondaryInfo;
321       return;
322     }
323     savedSecondaryInfo = null;
324     FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
325     Fragment oldBanner = getChildFragmentManager().findFragmentById(R.id.incall_on_hold_banner);
326     if (secondaryInfo.shouldShow()) {
327       transaction.replace(R.id.incall_on_hold_banner, OnHoldFragment.newInstance(secondaryInfo));
328     } else {
329       if (oldBanner != null) {
330         transaction.remove(oldBanner);
331       }
332     }
333     transaction.setCustomAnimations(R.anim.abc_slide_in_top, R.anim.abc_slide_out_top);
334     transaction.commitNowAllowingStateLoss();
335   }
336 
337   @Override
setCallState(@onNull PrimaryCallState primaryCallState)338   public void setCallState(@NonNull PrimaryCallState primaryCallState) {
339     LogUtil.i("InCallFragment.setCallState", primaryCallState.toString());
340     contactGridManager.setCallState(primaryCallState);
341     getButtonController(InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY)
342         .setAllowed(primaryCallState.swapToSecondaryButtonState() != ButtonState.NOT_SUPPORT);
343     getButtonController(InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY)
344         .setEnabled(primaryCallState.swapToSecondaryButtonState() == ButtonState.ENABLED);
345     buttonChooser =
346         ButtonChooserFactory.newButtonChooser(
347             voiceNetworkType, primaryCallState.isWifi(), phoneType);
348     updateButtonStates();
349   }
350 
351   @Override
setEndCallButtonEnabled(boolean enabled, boolean animate)352   public void setEndCallButtonEnabled(boolean enabled, boolean animate) {
353     if (endCallButton != null) {
354       endCallButton.setEnabled(enabled);
355     }
356   }
357 
358   @Override
showManageConferenceCallButton(boolean visible)359   public void showManageConferenceCallButton(boolean visible) {
360     getButtonController(InCallButtonIds.BUTTON_MANAGE_VOICE_CONFERENCE).setAllowed(visible);
361     getButtonController(InCallButtonIds.BUTTON_MANAGE_VOICE_CONFERENCE).setEnabled(visible);
362     updateButtonStates();
363   }
364 
365   @Override
isManageConferenceVisible()366   public boolean isManageConferenceVisible() {
367     return getButtonController(InCallButtonIds.BUTTON_MANAGE_VOICE_CONFERENCE).isAllowed();
368   }
369 
370   @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)371   public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
372     contactGridManager.dispatchPopulateAccessibilityEvent(event);
373   }
374 
375   @Override
showNoteSentToast()376   public void showNoteSentToast() {
377     LogUtil.i("InCallFragment.showNoteSentToast", null);
378     Toast.makeText(getContext(), R.string.incall_note_sent, Toast.LENGTH_LONG).show();
379   }
380 
381   @Override
updateInCallScreenColors()382   public void updateInCallScreenColors() {}
383 
384   @Override
onInCallScreenDialpadVisibilityChange(boolean isShowing)385   public void onInCallScreenDialpadVisibilityChange(boolean isShowing) {
386     LogUtil.i("InCallFragment.onInCallScreenDialpadVisibilityChange", "isShowing: " + isShowing);
387     // Take note that the dialpad button isShowing
388     getButtonController(InCallButtonIds.BUTTON_DIALPAD).setChecked(isShowing);
389 
390     // This check is needed because there is a race condition where we attempt to update
391     // ButtonGridFragment before it is ready, so we check whether it is ready first and once it is
392     // ready, #onButtonGridCreated will mark the dialpad button as isShowing.
393     if (inCallButtonGridFragment != null) {
394       // Update the Android Button's state to isShowing.
395       inCallButtonGridFragment.onInCallScreenDialpadVisibilityChange(isShowing);
396     }
397     Activity activity = getActivity();
398     Window window = activity.getWindow();
399     window.setNavigationBarColor(
400         activity.getColor(
401             isShowing ? android.R.color.background_dark : android.R.color.transparent));
402   }
403 
404   @Override
getAnswerAndDialpadContainerResourceId()405   public int getAnswerAndDialpadContainerResourceId() {
406     return R.id.incall_dialpad_container;
407   }
408 
409   @Override
getInCallScreenFragment()410   public Fragment getInCallScreenFragment() {
411     return this;
412   }
413 
414   @Override
showButton(@nCallButtonIds int buttonId, boolean show)415   public void showButton(@InCallButtonIds int buttonId, boolean show) {
416     LogUtil.v(
417         "InCallFragment.showButton",
418         "buttionId: %s, show: %b",
419         InCallButtonIdsExtension.toString(buttonId),
420         show);
421     if (isSupportedButton(buttonId)) {
422       getButtonController(buttonId).setAllowed(show);
423       if (buttonId == InCallButtonIds.BUTTON_UPGRADE_TO_VIDEO && show) {
424         Logger.get(getContext())
425             .logImpression(DialerImpression.Type.UPGRADE_TO_VIDEO_CALL_BUTTON_SHOWN);
426       }
427     }
428   }
429 
430   @Override
enableButton(@nCallButtonIds int buttonId, boolean enable)431   public void enableButton(@InCallButtonIds int buttonId, boolean enable) {
432     LogUtil.v(
433         "InCallFragment.enableButton",
434         "buttonId: %s, enable: %b",
435         InCallButtonIdsExtension.toString(buttonId),
436         enable);
437     if (isSupportedButton(buttonId)) {
438       getButtonController(buttonId).setEnabled(enable);
439     }
440   }
441 
442   @Override
setEnabled(boolean enabled)443   public void setEnabled(boolean enabled) {
444     LogUtil.v("InCallFragment.setEnabled", "enabled: " + enabled);
445     for (ButtonController buttonController : buttonControllers) {
446       buttonController.setEnabled(enabled);
447     }
448   }
449 
450   @Override
setHold(boolean value)451   public void setHold(boolean value) {
452     getButtonController(InCallButtonIds.BUTTON_HOLD).setChecked(value);
453   }
454 
455   @Override
setCameraSwitched(boolean isBackFacingCamera)456   public void setCameraSwitched(boolean isBackFacingCamera) {}
457 
458   @Override
setVideoPaused(boolean isPaused)459   public void setVideoPaused(boolean isPaused) {}
460 
461   @Override
setAudioState(CallAudioState audioState)462   public void setAudioState(CallAudioState audioState) {
463     LogUtil.i("InCallFragment.setAudioState", "audioState: " + audioState);
464     ((SpeakerButtonController) getButtonController(InCallButtonIds.BUTTON_AUDIO))
465         .setAudioState(audioState);
466     getButtonController(InCallButtonIds.BUTTON_MUTE).setChecked(audioState.isMuted());
467   }
468 
469   @Override
updateButtonStates()470   public void updateButtonStates() {
471     // When the incall screen is ready, this method is called from #setSecondary, even though the
472     // incall button ui is not ready yet. This method is called again once the incall button ui is
473     // ready though, so this operation is safe and will be executed asap.
474     if (inCallButtonGridFragment == null) {
475       return;
476     }
477     int numVisibleButtons =
478         inCallButtonGridFragment.updateButtonStates(
479             buttonControllers, buttonChooser, voiceNetworkType, phoneType);
480 
481     int visibility = numVisibleButtons == 0 ? View.GONE : View.VISIBLE;
482     pager.setVisibility(visibility);
483     if (adapter != null
484         && adapter.getCount() > 1
485         && getResources().getInteger(R.integer.incall_num_rows) > 1) {
486       paginator.setVisibility(View.VISIBLE);
487       pager.setSwipingLocked(false);
488     } else {
489       paginator.setVisibility(View.GONE);
490       if (adapter != null) {
491         pager.setSwipingLocked(true);
492         pager.setCurrentItem(adapter.getButtonGridPosition());
493       }
494     }
495   }
496 
497   @Override
updateInCallButtonUiColors(@olorInt int color)498   public void updateInCallButtonUiColors(@ColorInt int color) {
499     inCallButtonGridFragment.updateButtonColor(color);
500   }
501 
502   @Override
getInCallButtonUiFragment()503   public Fragment getInCallButtonUiFragment() {
504     return this;
505   }
506 
507   @Override
showAudioRouteSelector()508   public void showAudioRouteSelector() {
509     AudioRouteSelectorDialogFragment.newInstance(inCallButtonUiDelegate.getCurrentAudioState())
510         .show(getChildFragmentManager(), null);
511   }
512 
513   @Override
onAudioRouteSelected(int audioRoute)514   public void onAudioRouteSelected(int audioRoute) {
515     inCallButtonUiDelegate.setAudioRoute(audioRoute);
516   }
517 
518   @Override
onAudioRouteSelectorDismiss()519   public void onAudioRouteSelectorDismiss() {}
520 
521   @NonNull
522   @Override
getButtonController(@nCallButtonIds int id)523   public ButtonController getButtonController(@InCallButtonIds int id) {
524     for (ButtonController buttonController : buttonControllers) {
525       if (buttonController.getInCallButtonId() == id) {
526         return buttonController;
527       }
528     }
529     Assert.fail();
530     return null;
531   }
532 
533   @Override
onButtonGridCreated(InCallButtonGridFragment inCallButtonGridFragment)534   public void onButtonGridCreated(InCallButtonGridFragment inCallButtonGridFragment) {
535     LogUtil.i("InCallFragment.onButtonGridCreated", "InCallUiReady");
536     this.inCallButtonGridFragment = inCallButtonGridFragment;
537     inCallButtonUiDelegate.onInCallButtonUiReady(this);
538     updateButtonStates();
539   }
540 
541   @Override
onButtonGridDestroyed()542   public void onButtonGridDestroyed() {
543     LogUtil.i("InCallFragment.onButtonGridCreated", "InCallUiUnready");
544     inCallButtonUiDelegate.onInCallButtonUiUnready();
545     this.inCallButtonGridFragment = null;
546   }
547 
548   @Override
isShowingLocationUi()549   public boolean isShowingLocationUi() {
550     Fragment fragment = getLocationFragment();
551     return fragment != null && fragment.isVisible();
552   }
553 
554   @Override
showLocationUi(@ullable Fragment locationUi)555   public void showLocationUi(@Nullable Fragment locationUi) {
556     boolean isVisible = isShowingLocationUi();
557     if (locationUi != null && !isVisible) {
558       // Show the location fragment.
559       getChildFragmentManager()
560           .beginTransaction()
561           .replace(R.id.incall_location_holder, locationUi)
562           .commitAllowingStateLoss();
563     } else if (locationUi == null && isVisible) {
564       // Hide the location fragment
565       getChildFragmentManager()
566           .beginTransaction()
567           .remove(getLocationFragment())
568           .commitAllowingStateLoss();
569     }
570   }
571 
572   @Override
onMultiWindowModeChanged(boolean isInMultiWindowMode)573   public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
574     super.onMultiWindowModeChanged(isInMultiWindowMode);
575     if (isInMultiWindowMode == isShowingLocationUi()) {
576       LogUtil.i("InCallFragment.onMultiWindowModeChanged", "hide = " + isInMultiWindowMode);
577       // Need to show or hide location
578       showLocationUi(isInMultiWindowMode ? null : getLocationFragment());
579     }
580     contactGridManager.onMultiWindowModeChanged(isInMultiWindowMode);
581   }
582 
getLocationFragment()583   private Fragment getLocationFragment() {
584     return getChildFragmentManager().findFragmentById(R.id.incall_location_holder);
585   }
586 }
587