blob: 871243c7d852750a2222c8d740f8f6cb3bff79ee [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
2 * Copyright (C) 2013 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
17package com.android.incallui;
18
19import static com.android.contacts.common.compat.CallCompat.Details.PROPERTY_ENTERPRISE_CALL;
20
21import android.Manifest;
Eric Erfanianccca3152017-02-22 16:32:36 -080022import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.PackageManager;
27import android.graphics.drawable.Drawable;
28import android.hardware.display.DisplayManager;
29import android.os.BatteryManager;
30import android.os.Handler;
Eric Erfanian2ca43182017-08-31 06:57:16 -070031import android.os.Trace;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070032import android.support.annotation.NonNull;
Eric Erfanianccca3152017-02-22 16:32:36 -080033import android.support.annotation.Nullable;
34import android.support.v4.app.Fragment;
35import android.support.v4.content.ContextCompat;
36import android.telecom.Call.Details;
37import android.telecom.StatusHints;
38import android.telecom.TelecomManager;
39import android.text.TextUtils;
40import android.view.Display;
41import android.view.View;
42import android.view.accessibility.AccessibilityEvent;
43import android.view.accessibility.AccessibilityManager;
44import com.android.contacts.common.ContactsUtils;
45import com.android.contacts.common.preference.ContactsPreferences;
46import com.android.contacts.common.util.ContactDisplayUtils;
47import com.android.dialer.common.Assert;
Eric Erfanianccca3152017-02-22 16:32:36 -080048import com.android.dialer.common.LogUtil;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070049import com.android.dialer.compat.ActivityCompat;
Eric Erfanian2ca43182017-08-31 06:57:16 -070050import com.android.dialer.configprovider.ConfigProviderBindings;
Eric Erfanian8369df02017-05-03 10:27:13 -070051import com.android.dialer.logging.DialerImpression;
Eric Erfaniand8046e52017-04-06 09:41:50 -070052import com.android.dialer.logging.Logger;
Eric Erfanianccca3152017-02-22 16:32:36 -080053import com.android.dialer.multimedia.MultimediaData;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070054import com.android.dialer.oem.MotorolaUtils;
wangqi97539352017-09-25 11:15:16 -070055import com.android.dialer.phonenumberutil.PhoneNumberHelper;
Eric Erfanian2ca43182017-08-31 06:57:16 -070056import com.android.dialer.postcall.PostCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080057import com.android.incallui.ContactInfoCache.ContactCacheEntry;
58import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
59import com.android.incallui.InCallPresenter.InCallDetailsListener;
60import com.android.incallui.InCallPresenter.InCallEventListener;
61import com.android.incallui.InCallPresenter.InCallState;
62import com.android.incallui.InCallPresenter.InCallStateListener;
63import com.android.incallui.InCallPresenter.IncomingCallListener;
64import com.android.incallui.call.CallList;
65import com.android.incallui.call.DialerCall;
Eric Erfanian2ca43182017-08-31 06:57:16 -070066import com.android.incallui.call.DialerCall.State;
Eric Erfanianccca3152017-02-22 16:32:36 -080067import com.android.incallui.call.DialerCallListener;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070068import com.android.incallui.calllocation.CallLocation;
69import com.android.incallui.calllocation.CallLocationComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -080070import com.android.incallui.incall.protocol.ContactPhotoType;
71import com.android.incallui.incall.protocol.InCallScreen;
72import com.android.incallui.incall.protocol.InCallScreenDelegate;
73import com.android.incallui.incall.protocol.PrimaryCallState;
Eric Erfanian2ca43182017-08-31 06:57:16 -070074import com.android.incallui.incall.protocol.PrimaryCallState.ButtonState;
Eric Erfanianccca3152017-02-22 16:32:36 -080075import com.android.incallui.incall.protocol.PrimaryInfo;
76import com.android.incallui.incall.protocol.SecondaryInfo;
Eric Erfanian90508232017-03-24 09:31:16 -070077import com.android.incallui.videotech.utils.SessionModificationState;
Eric Erfanianccca3152017-02-22 16:32:36 -080078import java.lang.ref.WeakReference;
79
80/**
81 * Controller for the Call Card Fragment. This class listens for changes to InCallState and passes
82 * it along to the fragment.
83 */
84public class CallCardPresenter
85 implements InCallStateListener,
86 IncomingCallListener,
87 InCallDetailsListener,
88 InCallEventListener,
89 InCallScreenDelegate,
Eric Erfanian2ca43182017-08-31 06:57:16 -070090 DialerCallListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080091
92 /**
93 * Amount of time to wait before sending an announcement via the accessibility manager. When the
94 * call state changes to an outgoing or incoming state for the first time, the UI can often be
95 * changing due to call updates or contact lookup. This allows the UI to settle to a stable state
96 * to ensure that the correct information is announced.
97 */
98 private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS = 500;
99
100 /** Flag to allow the user's current location to be shown during emergency calls. */
101 private static final String CONFIG_ENABLE_EMERGENCY_LOCATION = "config_enable_emergency_location";
102
103 private static final boolean CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT = true;
104
105 /**
106 * Make it possible to not get location during an emergency call if the battery is too low, since
107 * doing so could trigger gps and thus potentially cause the phone to die in the middle of the
108 * call.
109 */
110 private static final String CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION =
111 "min_battery_percent_for_emergency_location";
112
113 private static final long CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT = 10;
114
115 private final Context mContext;
116 private final Handler handler = new Handler();
117
118 private DialerCall mPrimary;
119 private DialerCall mSecondary;
120 private ContactCacheEntry mPrimaryContactInfo;
121 private ContactCacheEntry mSecondaryContactInfo;
122 @Nullable private ContactsPreferences mContactsPreferences;
123 private boolean mIsFullscreen = false;
124 private InCallScreen mInCallScreen;
125 private boolean isInCallScreenReady;
126 private boolean shouldSendAccessibilityEvent;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700127
128 @NonNull private final CallLocation callLocation;
Eric Erfanianccca3152017-02-22 16:32:36 -0800129 private final Runnable sendAccessibilityEventRunnable =
130 new Runnable() {
131 @Override
132 public void run() {
133 shouldSendAccessibilityEvent = !sendAccessibilityEvent(mContext, getUi());
134 LogUtil.i(
135 "CallCardPresenter.sendAccessibilityEventRunnable",
136 "still should send: %b",
137 shouldSendAccessibilityEvent);
138 if (!shouldSendAccessibilityEvent) {
139 handler.removeCallbacks(this);
140 }
141 }
142 };
143
144 public CallCardPresenter(Context context) {
wangqi385a5a12017-09-28 10:44:54 -0700145 LogUtil.i("CallCardPresenter.constructor", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800146 mContext = Assert.isNotNull(context).getApplicationContext();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700147 callLocation = CallLocationComponent.get(mContext).getCallLocation();
Eric Erfanianccca3152017-02-22 16:32:36 -0800148 }
149
150 private static boolean hasCallSubject(DialerCall call) {
151 return !TextUtils.isEmpty(call.getCallSubject());
152 }
153
154 @Override
155 public void onInCallScreenDelegateInit(InCallScreen inCallScreen) {
156 Assert.isNotNull(inCallScreen);
157 mInCallScreen = inCallScreen;
158 mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext);
159
160 // Call may be null if disconnect happened already.
161 DialerCall call = CallList.getInstance().getFirstCall();
162 if (call != null) {
163 mPrimary = call;
164 if (shouldShowNoteSentToast(mPrimary)) {
165 mInCallScreen.showNoteSentToast();
166 }
167 call.addListener(this);
168
169 // start processing lookups right away.
170 if (!call.isConferenceCall()) {
171 startContactInfoSearch(call, true, call.getState() == DialerCall.State.INCOMING);
172 } else {
173 updateContactEntry(null, true);
174 }
175 }
176
177 onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
178 }
179
180 @Override
181 public void onInCallScreenReady() {
wangqi385a5a12017-09-28 10:44:54 -0700182 LogUtil.i("CallCardPresenter.onInCallScreenReady", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800183 Assert.checkState(!isInCallScreenReady);
184 if (mContactsPreferences != null) {
185 mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
186 }
187
Eric Erfanianccca3152017-02-22 16:32:36 -0800188 // Contact search may have completed before ui is ready.
189 if (mPrimaryContactInfo != null) {
190 updatePrimaryDisplayInfo();
191 }
192
193 // Register for call state changes last
194 InCallPresenter.getInstance().addListener(this);
195 InCallPresenter.getInstance().addIncomingCallListener(this);
196 InCallPresenter.getInstance().addDetailsListener(this);
197 InCallPresenter.getInstance().addInCallEventListener(this);
198 isInCallScreenReady = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700199
Eric Erfaniand8046e52017-04-06 09:41:50 -0700200 // Log location impressions
201 if (isOutgoingEmergencyCall(mPrimary)) {
202 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_NEW_EMERGENCY_CALL);
203 } else if (isIncomingEmergencyCall(mPrimary) || isIncomingEmergencyCall(mSecondary)) {
204 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_CALLBACK);
205 }
206
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700207 // Showing the location may have been skipped if the UI wasn't ready during previous layout.
208 if (shouldShowLocation()) {
209 updatePrimaryDisplayInfo();
Eric Erfaniand8046e52017-04-06 09:41:50 -0700210
211 // Log location impressions
212 if (!hasLocationPermission()) {
213 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_NO_LOCATION_PERMISSION);
214 } else if (isBatteryTooLowForEmergencyLocation()) {
215 Logger.get(mContext)
216 .logImpression(DialerImpression.Type.EMERGENCY_BATTERY_TOO_LOW_TO_GET_LOCATION);
217 } else if (!callLocation.canGetLocation(mContext)) {
218 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_CANT_GET_LOCATION);
219 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700220 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800221 }
222
223 @Override
224 public void onInCallScreenUnready() {
wangqi385a5a12017-09-28 10:44:54 -0700225 LogUtil.i("CallCardPresenter.onInCallScreenUnready", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800226 Assert.checkState(isInCallScreenReady);
227
Eric Erfanianccca3152017-02-22 16:32:36 -0800228 // stop getting call state changes
229 InCallPresenter.getInstance().removeListener(this);
230 InCallPresenter.getInstance().removeIncomingCallListener(this);
231 InCallPresenter.getInstance().removeDetailsListener(this);
232 InCallPresenter.getInstance().removeInCallEventListener(this);
233 if (mPrimary != null) {
234 mPrimary.removeListener(this);
235 }
236
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700237 callLocation.close();
238
Eric Erfanianccca3152017-02-22 16:32:36 -0800239 mPrimary = null;
240 mPrimaryContactInfo = null;
241 mSecondaryContactInfo = null;
242 isInCallScreenReady = false;
243 }
244
245 @Override
246 public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
247 // same logic should happen as with onStateChange()
248 onStateChange(oldState, newState, CallList.getInstance());
249 }
250
251 @Override
252 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700253 Trace.beginSection("CallCardPresenter.onStateChange");
254 LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800255 if (mInCallScreen == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700256 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800257 return;
258 }
259
260 DialerCall primary = null;
261 DialerCall secondary = null;
262
263 if (newState == InCallState.INCOMING) {
264 primary = callList.getIncomingCall();
265 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
266 primary = callList.getOutgoingCall();
267 if (primary == null) {
268 primary = callList.getPendingOutgoingCall();
269 }
270
271 // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
272 // highest priority call to display as the secondary call.
273 secondary = getCallToDisplay(callList, null, true);
274 } else if (newState == InCallState.INCALL) {
275 primary = getCallToDisplay(callList, null, false);
276 secondary = getCallToDisplay(callList, primary, true);
277 }
278
279 LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
280 LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
281
282 final boolean primaryChanged =
283 !(DialerCall.areSame(mPrimary, primary) && DialerCall.areSameNumber(mPrimary, primary));
284 final boolean secondaryChanged =
285 !(DialerCall.areSame(mSecondary, secondary)
286 && DialerCall.areSameNumber(mSecondary, secondary));
287
288 mSecondary = secondary;
289 DialerCall previousPrimary = mPrimary;
290 mPrimary = primary;
291
292 if (mPrimary != null) {
293 InCallPresenter.getInstance().onForegroundCallChanged(mPrimary);
294 mInCallScreen.updateInCallScreenColors();
295 }
296
297 if (primaryChanged && shouldShowNoteSentToast(primary)) {
298 mInCallScreen.showNoteSentToast();
299 }
300
301 // Refresh primary call information if either:
302 // 1. Primary call changed.
303 // 2. The call's ability to manage conference has changed.
304 if (shouldRefreshPrimaryInfo(primaryChanged)) {
305 // primary call has changed
306 if (previousPrimary != null) {
307 previousPrimary.removeListener(this);
308 }
309 mPrimary.addListener(this);
310
311 mPrimaryContactInfo =
312 ContactInfoCache.buildCacheEntryFromCall(
313 mContext, mPrimary, mPrimary.getState() == DialerCall.State.INCOMING);
314 updatePrimaryDisplayInfo();
315 maybeStartSearch(mPrimary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800316 }
317
318 if (previousPrimary != null && mPrimary == null) {
319 previousPrimary.removeListener(this);
320 }
321
322 if (mSecondary == null) {
323 // Secondary call may have ended. Update the ui.
324 mSecondaryContactInfo = null;
325 updateSecondaryDisplayInfo();
326 } else if (secondaryChanged) {
327 // secondary call has changed
328 mSecondaryContactInfo =
329 ContactInfoCache.buildCacheEntryFromCall(
330 mContext, mSecondary, mSecondary.getState() == DialerCall.State.INCOMING);
331 updateSecondaryDisplayInfo();
332 maybeStartSearch(mSecondary, false);
Eric Erfanianccca3152017-02-22 16:32:36 -0800333 }
334
335 // Set the call state
336 int callState = DialerCall.State.IDLE;
337 if (mPrimary != null) {
338 callState = mPrimary.getState();
339 updatePrimaryCallState();
340 } else {
341 getUi().setCallState(PrimaryCallState.createEmptyPrimaryCallState());
342 }
343
344 maybeShowManageConferenceCallButton();
345
346 // Hide the end call button instantly if we're receiving an incoming call.
347 getUi()
348 .setEndCallButtonEnabled(
349 shouldShowEndCallButton(mPrimary, callState),
350 callState != DialerCall.State.INCOMING /* animate */);
351
352 maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700353 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800354 }
355
356 @Override
357 public void onDetailsChanged(DialerCall call, Details details) {
358 updatePrimaryCallState();
359
360 if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
361 != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
362 maybeShowManageConferenceCallButton();
363 }
364 }
365
366 @Override
367 public void onDialerCallDisconnect() {}
368
369 @Override
370 public void onDialerCallUpdate() {
371 // No-op; specific call updates handled elsewhere.
372 }
373
374 @Override
375 public void onWiFiToLteHandover() {}
376
377 @Override
378 public void onHandoverToWifiFailure() {}
379
Eric Erfanianc857f902017-05-15 14:05:33 -0700380 @Override
381 public void onInternationalCallOnWifi() {}
382
Eric Erfanian2ca43182017-08-31 06:57:16 -0700383 @Override
384 public void onEnrichedCallSessionUpdate() {
385 LogUtil.enterBlock("CallCardPresenter.onEnrichedCallSessionUpdate");
386 updatePrimaryDisplayInfo();
387 }
388
Eric Erfanianccca3152017-02-22 16:32:36 -0800389 /** Handles a change to the child number by refreshing the primary call info. */
390 @Override
391 public void onDialerCallChildNumberChange() {
392 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
393
394 if (mPrimary == null) {
395 return;
396 }
397 updatePrimaryDisplayInfo();
398 }
399
400 /** Handles a change to the last forwarding number by refreshing the primary call info. */
401 @Override
402 public void onDialerCallLastForwardedNumberChange() {
403 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
404
405 if (mPrimary == null) {
406 return;
407 }
408 updatePrimaryDisplayInfo();
409 updatePrimaryCallState();
410 }
411
412 @Override
413 public void onDialerCallUpgradeToVideo() {}
414
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700415 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800416 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700417 public void onDialerCallSessionModificationStateChange() {
418 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800419
420 if (mPrimary == null) {
421 return;
422 }
423 getUi()
424 .setEndCallButtonEnabled(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700425 mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700426 != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800427 true /* shouldAnimate */);
428 updatePrimaryCallState();
429 }
430
Eric Erfanianccca3152017-02-22 16:32:36 -0800431 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
432 if (mPrimary == null) {
433 return false;
434 }
435 return primaryChanged
436 || mInCallScreen.isManageConferenceVisible() != shouldShowManageConference();
437 }
438
439 private void updatePrimaryCallState() {
440 if (getUi() != null && mPrimary != null) {
441 boolean isWorkCall =
442 mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL)
443 || (mPrimaryContactInfo != null
444 && mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
445 boolean isHdAudioCall =
446 isPrimaryCallActive() && mPrimary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700447 boolean isAttemptingHdAudioCall =
448 !isHdAudioCall
449 && !mPrimary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
450 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(mContext);
451
452 boolean isBusiness = mPrimaryContactInfo != null && mPrimaryContactInfo.isBusiness;
453
Eric Erfanianccca3152017-02-22 16:32:36 -0800454 // Check for video state change and update the visibility of the contact photo. The contact
455 // photo is hidden when the incoming video surface is shown.
456 // The contact photo visibility can also change in setPrimary().
457 boolean shouldShowContactPhoto =
458 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
459 getUi()
460 .setCallState(
461 new PrimaryCallState(
462 mPrimary.getState(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700463 mPrimary.isVideoCall(),
464 mPrimary.getVideoTech().getSessionModificationState(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800465 mPrimary.getDisconnectCause(),
466 getConnectionLabel(),
467 getCallStateIcon(),
468 getGatewayNumber(),
469 shouldShowCallSubject(mPrimary) ? mPrimary.getCallSubject() : null,
wangqi97539352017-09-25 11:15:16 -0700470 PhoneNumberHelper.formatNumber(
471 mPrimary.getCallbackNumber(), mPrimary.getSimCountryIso()),
Eric Erfanianccca3152017-02-22 16:32:36 -0800472 mPrimary.hasProperty(Details.PROPERTY_WIFI),
Eric Erfanian83b20212017-05-31 08:53:10 -0700473 mPrimary.isConferenceCall()
474 && !mPrimary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE),
Eric Erfanianccca3152017-02-22 16:32:36 -0800475 isWorkCall,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700476 isAttemptingHdAudioCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800477 isHdAudioCall,
478 !TextUtils.isEmpty(mPrimary.getLastForwardedNumber()),
479 shouldShowContactPhoto,
480 mPrimary.getConnectTimeMillis(),
wangqi9982f0d2017-10-11 17:46:07 -0700481 mPrimary.isVoiceMailNumber(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700482 mPrimary.isRemotelyHeld(),
Eric Erfanian2ca43182017-08-31 06:57:16 -0700483 isBusiness,
484 supports2ndCallOnHold(),
485 getSwapToSecondaryButtonState(),
486 mPrimary.isAssistedDialed(),
erfaniand0f207f2017-10-11 12:23:29 -0700487 null,
488 mPrimary.getAssistedDialingExtras()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800489
490 InCallActivity activity =
491 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
492 if (activity != null) {
493 activity.onPrimaryCallStateChanged();
494 }
495 }
496 }
497
Eric Erfanian2ca43182017-08-31 06:57:16 -0700498 private @ButtonState int getSwapToSecondaryButtonState() {
499 if (mSecondary == null) {
500 return ButtonState.NOT_SUPPORT;
501 }
502 if (mPrimary.getState() == State.ACTIVE) {
503 return ButtonState.ENABLED;
504 }
505 return ButtonState.DISABLED;
506 }
507
Eric Erfanianccca3152017-02-22 16:32:36 -0800508 /** Only show the conference call button if we can manage the conference. */
509 private void maybeShowManageConferenceCallButton() {
510 getUi().showManageConferenceCallButton(shouldShowManageConference());
511 }
512
513 /**
514 * Determines if the manage conference button should be visible, based on the current primary
515 * call.
516 *
517 * @return {@code True} if the manage conference button should be visible.
518 */
519 private boolean shouldShowManageConference() {
520 if (mPrimary == null) {
521 return false;
522 }
523
524 return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)
525 && !mIsFullscreen;
526 }
527
Eric Erfanian2ca43182017-08-31 06:57:16 -0700528 private boolean supports2ndCallOnHold() {
529 DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
530 DialerCall incomingCall = CallList.getInstance().getIncomingCall();
531 if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
532 return incomingCall.can(Details.CAPABILITY_HOLD);
533 }
534 return true;
535 }
536
Eric Erfanianccca3152017-02-22 16:32:36 -0800537 @Override
538 public void onCallStateButtonClicked() {
539 Intent broadcastIntent = Bindings.get(mContext).getCallStateButtonBroadcastIntent(mContext);
540 if (broadcastIntent != null) {
541 LogUtil.v(
542 "CallCardPresenter.onCallStateButtonClicked",
543 "sending call state button broadcast: " + broadcastIntent);
544 mContext.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
545 }
546 }
547
548 @Override
549 public void onManageConferenceClicked() {
550 InCallActivity activity =
551 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
552 activity.showConferenceFragment(true);
553 }
554
555 @Override
556 public void onShrinkAnimationComplete() {
557 InCallPresenter.getInstance().onShrinkAnimationComplete();
558 }
559
Eric Erfanianccca3152017-02-22 16:32:36 -0800560 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
561 // no need to start search for conference calls which show generic info.
562 if (call != null && !call.isConferenceCall()) {
563 startContactInfoSearch(call, isPrimary, call.getState() == DialerCall.State.INCOMING);
564 }
565 }
566
Eric Erfanianccca3152017-02-22 16:32:36 -0800567 /** Starts a query for more contact data for the save primary and secondary calls. */
568 private void startContactInfoSearch(
569 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
570 final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
571
572 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
573 }
574
575 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
576 final boolean entryMatchesExistingCall =
577 (isPrimary && mPrimary != null && TextUtils.equals(callId, mPrimary.getId()))
578 || (!isPrimary && mSecondary != null && TextUtils.equals(callId, mSecondary.getId()));
579 if (entryMatchesExistingCall) {
580 updateContactEntry(entry, isPrimary);
581 } else {
582 LogUtil.e(
583 "CallCardPresenter.onContactInfoComplete",
584 "dropping stale contact lookup info for " + callId);
585 }
586
587 final DialerCall call = CallList.getInstance().getCallById(callId);
588 if (call != null) {
589 call.getLogState().contactLookupResult = entry.contactLookupResult;
590 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700591 if (entry.lookupUri != null) {
592 CallerInfoUtils.sendViewNotification(mContext, entry.lookupUri);
Eric Erfanianccca3152017-02-22 16:32:36 -0800593 }
594 }
595
596 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
597 if (getUi() == null) {
598 return;
599 }
600
601 if (entry.photo != null) {
602 if (mPrimary != null && callId.equals(mPrimary.getId())) {
603 updateContactEntry(entry, true /* isPrimary */);
604 } else if (mSecondary != null && callId.equals(mSecondary.getId())) {
605 updateContactEntry(entry, false /* isPrimary */);
606 }
607 }
608 }
609
610 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
611 if (isPrimary) {
612 mPrimaryContactInfo = entry;
613 updatePrimaryDisplayInfo();
614 } else {
615 mSecondaryContactInfo = entry;
616 updateSecondaryDisplayInfo();
617 }
618 }
619
620 /**
621 * Get the highest priority call to display. Goes through the calls and chooses which to return
622 * based on priority of which type of call to display to the user. Callers can use the "ignore"
623 * feature to get the second best call by passing a previously found primary call as ignore.
624 *
625 * @param ignore A call to ignore if found.
626 */
627 private DialerCall getCallToDisplay(
628 CallList callList, DialerCall ignore, boolean skipDisconnected) {
629 // Active calls come second. An active call always gets precedent.
630 DialerCall retval = callList.getActiveCall();
631 if (retval != null && retval != ignore) {
632 return retval;
633 }
634
635 // Sometimes there is intemediate state that two calls are in active even one is about
636 // to be on hold.
637 retval = callList.getSecondActiveCall();
638 if (retval != null && retval != ignore) {
639 return retval;
640 }
641
642 // Disconnected calls get primary position if there are no active calls
643 // to let user know quickly what call has disconnected. Disconnected
644 // calls are very short lived.
645 if (!skipDisconnected) {
646 retval = callList.getDisconnectingCall();
647 if (retval != null && retval != ignore) {
648 return retval;
649 }
650 retval = callList.getDisconnectedCall();
651 if (retval != null && retval != ignore) {
652 return retval;
653 }
654 }
655
656 // Then we go to background call (calls on hold)
657 retval = callList.getBackgroundCall();
658 if (retval != null && retval != ignore) {
659 return retval;
660 }
661
662 // Lastly, we go to a second background call.
663 retval = callList.getSecondBackgroundCall();
664
665 return retval;
666 }
667
668 private void updatePrimaryDisplayInfo() {
669 if (mInCallScreen == null) {
670 // TODO: May also occur if search result comes back after ui is destroyed. Look into
671 // removing that case completely.
672 LogUtil.v(
673 "CallCardPresenter.updatePrimaryDisplayInfo",
674 "updatePrimaryDisplayInfo called but ui is null!");
675 return;
676 }
677
678 if (mPrimary == null) {
679 // Clear the primary display info.
680 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
681 return;
682 }
683
684 // Hide the contact photo if we are in a video call and the incoming video surface is
685 // showing.
686 boolean showContactPhoto =
687 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
688
689 // DialerCall placed through a work phone account.
690 boolean hasWorkCallProperty = mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL);
691
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700692 MultimediaData multimediaData = null;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700693 if (mPrimary.getEnrichedCallSession() != null) {
694 multimediaData = mPrimary.getEnrichedCallSession().getMultimediaData();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700695 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800696
697 if (mPrimary.isConferenceCall()) {
698 LogUtil.v(
699 "CallCardPresenter.updatePrimaryDisplayInfo",
700 "update primary display info for conference call.");
701
702 mInCallScreen.setPrimary(
703 new PrimaryInfo(
704 null /* number */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700705 CallerInfoUtils.getConferenceString(
706 mContext, mPrimary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800707 false /* nameIsNumber */,
708 null /* location */,
709 null /* label */,
Eric Erfanian83b20212017-05-31 08:53:10 -0700710 null /* photo */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800711 ContactPhotoType.DEFAULT_PLACEHOLDER,
712 false /* isSipCall */,
713 showContactPhoto,
714 hasWorkCallProperty,
715 false /* isSpam */,
wangqiae6c8ec2017-09-28 17:39:40 -0700716 false /* isLocalContact */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800717 false /* answeringDisconnectsOngoingCall */,
718 shouldShowLocation(),
719 null /* contactInfoLookupKey */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700720 null /* enrichedCallMultimediaData */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700721 true /* showInCallButtonGrid */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700722 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800723 } else if (mPrimaryContactInfo != null) {
724 LogUtil.v(
725 "CallCardPresenter.updatePrimaryDisplayInfo",
726 "update primary display info for " + mPrimaryContactInfo);
727
728 String name = getNameForCall(mPrimaryContactInfo);
729 String number;
730
731 boolean isChildNumberShown = !TextUtils.isEmpty(mPrimary.getChildNumber());
732 boolean isForwardedNumberShown = !TextUtils.isEmpty(mPrimary.getLastForwardedNumber());
733 boolean isCallSubjectShown = shouldShowCallSubject(mPrimary);
734
735 if (isCallSubjectShown) {
736 number = null;
737 } else if (isChildNumberShown) {
738 number = mContext.getString(R.string.child_number, mPrimary.getChildNumber());
739 } else if (isForwardedNumberShown) {
740 // Use last forwarded number instead of second line, if present.
741 number = mPrimary.getLastForwardedNumber();
742 } else {
743 number = mPrimaryContactInfo.number;
744 }
745
746 boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700747
Eric Erfanianccca3152017-02-22 16:32:36 -0800748 // DialerCall with caller that is a work contact.
749 boolean isWorkContact = (mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
750 mInCallScreen.setPrimary(
751 new PrimaryInfo(
752 number,
Eric Erfanianc857f902017-05-15 14:05:33 -0700753 mPrimary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800754 nameIsNumber,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700755 shouldShowLocationAsLabel(nameIsNumber, mPrimaryContactInfo.shouldShowLocation)
756 ? mPrimaryContactInfo.location
757 : null,
Eric Erfanianccca3152017-02-22 16:32:36 -0800758 isChildNumberShown || isCallSubjectShown ? null : mPrimaryContactInfo.label,
759 mPrimaryContactInfo.photo,
760 mPrimaryContactInfo.photoType,
761 mPrimaryContactInfo.isSipCall,
762 showContactPhoto,
763 hasWorkCallProperty || isWorkContact,
764 mPrimary.isSpam(),
wangqiae6c8ec2017-09-28 17:39:40 -0700765 mPrimaryContactInfo.isLocalContact(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800766 mPrimary.answeringDisconnectsForegroundVideoCall(),
767 shouldShowLocation(),
768 mPrimaryContactInfo.lookupKey,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700769 multimediaData,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700770 true /* showInCallButtonGrid */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700771 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800772 } else {
773 // Clear the primary display info.
774 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
775 }
776
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700777 if (isInCallScreenReady) {
778 mInCallScreen.showLocationUi(getLocationFragment());
779 } else {
780 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
781 }
782 }
783
Eric Erfaniand8046e52017-04-06 09:41:50 -0700784 private static boolean shouldShowLocationAsLabel(
785 boolean nameIsNumber, boolean shouldShowLocation) {
786 if (nameIsNumber) {
787 return true;
788 }
789 if (shouldShowLocation) {
790 return true;
791 }
792 return false;
793 }
794
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700795 private Fragment getLocationFragment() {
796 if (!ConfigProviderBindings.get(mContext)
797 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
798 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
799 return null;
800 }
801 if (!shouldShowLocation()) {
802 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
803 return null;
804 }
805 if (!hasLocationPermission()) {
806 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
807 return null;
808 }
809 if (isBatteryTooLowForEmergencyLocation()) {
810 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
811 return null;
812 }
813 if (ActivityCompat.isInMultiWindowMode(mInCallScreen.getInCallScreenFragment().getActivity())) {
814 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
815 return null;
816 }
817 if (mPrimary.isVideoCall()) {
818 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
819 return null;
820 }
821 if (!callLocation.canGetLocation(mContext)) {
822 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
823 return null;
824 }
825 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
826 return callLocation.getLocationFragment(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800827 }
828
829 private boolean shouldShowLocation() {
830 if (isOutgoingEmergencyCall(mPrimary)) {
831 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
832 return true;
833 } else if (isIncomingEmergencyCall(mPrimary)) {
834 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
835 return true;
836 } else if (isIncomingEmergencyCall(mSecondary)) {
837 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
838 return true;
839 }
840 return false;
841 }
842
843 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
844 return call != null && !call.isIncoming() && call.isEmergencyCall();
845 }
846
847 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
848 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
849 }
850
851 private boolean hasLocationPermission() {
852 return ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION)
853 == PackageManager.PERMISSION_GRANTED;
854 }
855
856 private boolean isBatteryTooLowForEmergencyLocation() {
857 Intent batteryStatus =
858 mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
859 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
860 if (status == BatteryManager.BATTERY_STATUS_CHARGING
861 || status == BatteryManager.BATTERY_STATUS_FULL) {
862 // Plugged in or full battery
863 return false;
864 }
865 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
866 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
867 float batteryPercent = (100f * level) / scale;
868 long threshold =
869 ConfigProviderBindings.get(mContext)
870 .getLong(
871 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
872 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
873 LogUtil.i(
874 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
875 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
876 return batteryPercent < threshold;
877 }
878
879 private void updateSecondaryDisplayInfo() {
880 if (mInCallScreen == null) {
881 return;
882 }
883
884 if (mSecondary == null) {
885 // Clear the secondary display info.
886 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
887 return;
888 }
889
Eric Erfanian2ca43182017-08-31 06:57:16 -0700890 if (mSecondary.isMergeInProcess()) {
891 LogUtil.i(
892 "CallCardPresenter.updateSecondaryDisplayInfo",
893 "secondary call is merge in process, clearing info");
894 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
895 return;
896 }
897
Eric Erfanianccca3152017-02-22 16:32:36 -0800898 if (mSecondary.isConferenceCall()) {
899 mInCallScreen.setSecondary(
900 new SecondaryInfo(
901 true /* show */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700902 CallerInfoUtils.getConferenceString(
903 mContext, mSecondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800904 false /* nameIsNumber */,
905 null /* label */,
906 mSecondary.getCallProviderLabel(),
907 true /* isConference */,
908 mSecondary.isVideoCall(),
909 mIsFullscreen));
910 } else if (mSecondaryContactInfo != null) {
911 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + mSecondaryContactInfo);
912 String name = getNameForCall(mSecondaryContactInfo);
913 boolean nameIsNumber = name != null && name.equals(mSecondaryContactInfo.number);
914 mInCallScreen.setSecondary(
915 new SecondaryInfo(
916 true /* show */,
Eric Erfanianc857f902017-05-15 14:05:33 -0700917 mSecondary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800918 nameIsNumber,
919 mSecondaryContactInfo.label,
920 mSecondary.getCallProviderLabel(),
921 false /* isConference */,
922 mSecondary.isVideoCall(),
923 mIsFullscreen));
924 } else {
925 // Clear the secondary display info.
926 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
927 }
928 }
929
930 /** Returns the gateway number for any existing outgoing call. */
931 private String getGatewayNumber() {
932 if (hasOutgoingGatewayCall()) {
933 return DialerCall.getNumberFromHandle(mPrimary.getGatewayInfo().getGatewayAddress());
934 }
935 return null;
936 }
937
938 /**
939 * Returns the label (line of text above the number/name) for any given call. For example,
940 * "calling via [Account/Google Voice]" for outgoing calls.
941 */
942 private String getConnectionLabel() {
943 if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_PHONE_STATE)
944 != PackageManager.PERMISSION_GRANTED) {
945 return null;
946 }
947 StatusHints statusHints = mPrimary.getStatusHints();
948 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
949 return statusHints.getLabel().toString();
950 }
951
952 if (hasOutgoingGatewayCall() && getUi() != null) {
953 // Return the label for the gateway app on outgoing calls.
954 final PackageManager pm = mContext.getPackageManager();
955 try {
956 ApplicationInfo info =
957 pm.getApplicationInfo(mPrimary.getGatewayInfo().getGatewayProviderPackageName(), 0);
958 return pm.getApplicationLabel(info).toString();
959 } catch (PackageManager.NameNotFoundException e) {
960 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
961 return null;
962 }
963 }
964 return mPrimary.getCallProviderLabel();
965 }
966
967 private Drawable getCallStateIcon() {
968 // Return connection icon if one exists.
969 StatusHints statusHints = mPrimary.getStatusHints();
970 if (statusHints != null && statusHints.getIcon() != null) {
971 Drawable icon = statusHints.getIcon().loadDrawable(mContext);
972 if (icon != null) {
973 return icon;
974 }
975 }
976
977 return null;
978 }
979
980 private boolean hasOutgoingGatewayCall() {
981 // We only display the gateway information while STATE_DIALING so return false for any other
982 // call state.
983 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
984 // is also called after a contact search completes (call is not present yet). Split the
985 // UI update so it can receive independent updates.
986 if (mPrimary == null) {
987 return false;
988 }
989 return DialerCall.State.isDialing(mPrimary.getState())
990 && mPrimary.getGatewayInfo() != null
991 && !mPrimary.getGatewayInfo().isEmpty();
992 }
993
994 /** Gets the name to display for the call. */
Eric Erfanian2ca43182017-08-31 06:57:16 -0700995 private String getNameForCall(ContactCacheEntry contactInfo) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800996 String preferredName =
997 ContactDisplayUtils.getPreferredDisplayName(
998 contactInfo.namePrimary, contactInfo.nameAlternative, mContactsPreferences);
999 if (TextUtils.isEmpty(preferredName)) {
1000 return contactInfo.number;
1001 }
1002 return preferredName;
1003 }
1004
Eric Erfanianccca3152017-02-22 16:32:36 -08001005 @Override
1006 public void onSecondaryInfoClicked() {
1007 if (mSecondary == null) {
1008 LogUtil.e(
1009 "CallCardPresenter.onSecondaryInfoClicked",
1010 "secondary info clicked but no secondary call.");
1011 return;
1012 }
1013
Eric Erfanian2ca43182017-08-31 06:57:16 -07001014 Logger.get(mContext)
1015 .logCallImpression(
1016 DialerImpression.Type.IN_CALL_SWAP_SECONDARY_BUTTON_PRESSED,
1017 mPrimary.getUniqueCallId(),
1018 mPrimary.getTimeAddedMs());
Eric Erfanianccca3152017-02-22 16:32:36 -08001019 LogUtil.i(
1020 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + mSecondary);
1021 mSecondary.unhold();
1022 }
1023
1024 @Override
1025 public void onEndCallClicked() {
1026 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + mPrimary);
1027 if (mPrimary != null) {
1028 mPrimary.disconnect();
1029 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001030 PostCall.onDisconnectPressed(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -08001031 }
1032
1033 /**
1034 * Handles a change to the fullscreen mode of the in-call UI.
1035 *
1036 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
1037 */
1038 @Override
1039 public void onFullscreenModeChanged(boolean isFullscreenMode) {
1040 mIsFullscreen = isFullscreenMode;
1041 if (mInCallScreen == null) {
1042 return;
1043 }
1044 maybeShowManageConferenceCallButton();
1045 }
1046
1047 private boolean isPrimaryCallActive() {
1048 return mPrimary != null && mPrimary.getState() == DialerCall.State.ACTIVE;
1049 }
1050
Eric Erfanianccca3152017-02-22 16:32:36 -08001051 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1052 if (primary == null) {
1053 return false;
1054 }
1055 if ((!DialerCall.State.isConnectingOrConnected(callState)
1056 && callState != DialerCall.State.DISCONNECTING
1057 && callState != DialerCall.State.DISCONNECTED)
1058 || callState == DialerCall.State.INCOMING) {
1059 return false;
1060 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001061 if (mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001062 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001063 return false;
1064 }
1065 return true;
1066 }
1067
1068 @Override
1069 public void onInCallScreenResumed() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001070 updatePrimaryDisplayInfo();
1071
Eric Erfanianccca3152017-02-22 16:32:36 -08001072 if (shouldSendAccessibilityEvent) {
1073 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1074 }
1075 }
1076
Eric Erfaniand8046e52017-04-06 09:41:50 -07001077 @Override
Eric Erfanian2ca43182017-08-31 06:57:16 -07001078 public void onInCallScreenPaused() {}
Eric Erfaniand8046e52017-04-06 09:41:50 -07001079
Eric Erfanianccca3152017-02-22 16:32:36 -08001080 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1081 AccessibilityManager am =
1082 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1083 if (!am.isEnabled()) {
1084 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1085 return false;
1086 }
1087 if (inCallScreen == null) {
1088 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1089 return false;
1090 }
1091 Fragment fragment = inCallScreen.getInCallScreenFragment();
1092 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1093 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1094 return false;
1095 }
1096
1097 DisplayManager displayManager =
1098 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1099 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1100 boolean screenIsOn = display.getState() == Display.STATE_ON;
1101 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1102 if (!screenIsOn) {
1103 return false;
1104 }
1105
1106 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1107 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1108 View view = inCallScreen.getInCallScreenFragment().getView();
1109 view.getParent().requestSendAccessibilityEvent(view, event);
1110 return true;
1111 }
1112
1113 private void maybeSendAccessibilityEvent(
1114 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1115 shouldSendAccessibilityEvent = false;
1116 if (mContext == null) {
1117 return;
1118 }
1119 final AccessibilityManager am =
1120 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
1121 if (!am.isEnabled()) {
1122 return;
1123 }
1124 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1125 // due to switching calls between two ongoing calls (one is on hold).
1126 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1127 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1128 || primaryChanged) {
1129 LogUtil.i(
1130 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1131 shouldSendAccessibilityEvent = true;
1132 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1133 }
1134 }
1135
1136 /**
1137 * Determines whether the call subject should be visible on the UI. For the call subject to be
1138 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1139 *
1140 * @param call The call.
1141 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1142 */
1143 private boolean shouldShowCallSubject(DialerCall call) {
1144 if (call == null) {
1145 return false;
1146 }
1147
1148 boolean isIncomingOrWaiting =
1149 mPrimary.getState() == DialerCall.State.INCOMING
1150 || mPrimary.getState() == DialerCall.State.CALL_WAITING;
1151 return isIncomingOrWaiting
1152 && !TextUtils.isEmpty(call.getCallSubject())
1153 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1154 && call.isCallSubjectSupported();
1155 }
1156
1157 /**
1158 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1159 * call with a subject.
1160 *
1161 * @param call The call
1162 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1163 */
1164 private boolean shouldShowNoteSentToast(DialerCall call) {
1165 return call != null
1166 && hasCallSubject(call)
1167 && (call.getState() == DialerCall.State.DIALING
1168 || call.getState() == DialerCall.State.CONNECTING);
1169 }
1170
1171 private InCallScreen getUi() {
1172 return mInCallScreen;
1173 }
1174
1175 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1176
1177 private final WeakReference<CallCardPresenter> mCallCardPresenter;
1178 private final boolean mIsPrimary;
1179
1180 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
1181 mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1182 mIsPrimary = isPrimary;
1183 }
1184
1185 @Override
1186 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
1187 CallCardPresenter presenter = mCallCardPresenter.get();
1188 if (presenter != null) {
1189 presenter.onContactInfoComplete(callId, entry, mIsPrimary);
1190 }
1191 }
1192
1193 @Override
1194 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
1195 CallCardPresenter presenter = mCallCardPresenter.get();
1196 if (presenter != null) {
1197 presenter.onImageLoadComplete(callId, entry);
1198 }
1199 }
1200 }
1201}