blob: 0ef0c9827916bb75ab08f57b2afad485e5d6a8d8 [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
wangqic8cf79e2017-10-17 09:21:00 -0700322 if (secondaryChanged) {
323 if (mSecondary == null) {
324 // Secondary call may have ended. Update the ui.
325 mSecondaryContactInfo = null;
326 updateSecondaryDisplayInfo();
327 } else {
328 // secondary call has changed
329 mSecondaryContactInfo =
330 ContactInfoCache.buildCacheEntryFromCall(
331 mContext, mSecondary, mSecondary.getState() == DialerCall.State.INCOMING);
332 updateSecondaryDisplayInfo();
333 maybeStartSearch(mSecondary, false);
334 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800335 }
336
337 // Set the call state
338 int callState = DialerCall.State.IDLE;
339 if (mPrimary != null) {
340 callState = mPrimary.getState();
341 updatePrimaryCallState();
342 } else {
343 getUi().setCallState(PrimaryCallState.createEmptyPrimaryCallState());
344 }
345
346 maybeShowManageConferenceCallButton();
347
348 // Hide the end call button instantly if we're receiving an incoming call.
349 getUi()
350 .setEndCallButtonEnabled(
351 shouldShowEndCallButton(mPrimary, callState),
352 callState != DialerCall.State.INCOMING /* animate */);
353
354 maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700355 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800356 }
357
358 @Override
359 public void onDetailsChanged(DialerCall call, Details details) {
360 updatePrimaryCallState();
361
362 if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
363 != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
364 maybeShowManageConferenceCallButton();
365 }
366 }
367
368 @Override
369 public void onDialerCallDisconnect() {}
370
371 @Override
372 public void onDialerCallUpdate() {
373 // No-op; specific call updates handled elsewhere.
374 }
375
376 @Override
377 public void onWiFiToLteHandover() {}
378
379 @Override
380 public void onHandoverToWifiFailure() {}
381
Eric Erfanianc857f902017-05-15 14:05:33 -0700382 @Override
383 public void onInternationalCallOnWifi() {}
384
Eric Erfanian2ca43182017-08-31 06:57:16 -0700385 @Override
386 public void onEnrichedCallSessionUpdate() {
387 LogUtil.enterBlock("CallCardPresenter.onEnrichedCallSessionUpdate");
388 updatePrimaryDisplayInfo();
389 }
390
Eric Erfanianccca3152017-02-22 16:32:36 -0800391 /** Handles a change to the child number by refreshing the primary call info. */
392 @Override
393 public void onDialerCallChildNumberChange() {
394 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
395
396 if (mPrimary == null) {
397 return;
398 }
399 updatePrimaryDisplayInfo();
400 }
401
402 /** Handles a change to the last forwarding number by refreshing the primary call info. */
403 @Override
404 public void onDialerCallLastForwardedNumberChange() {
405 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
406
407 if (mPrimary == null) {
408 return;
409 }
410 updatePrimaryDisplayInfo();
411 updatePrimaryCallState();
412 }
413
414 @Override
415 public void onDialerCallUpgradeToVideo() {}
416
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700417 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800418 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700419 public void onDialerCallSessionModificationStateChange() {
420 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800421
422 if (mPrimary == null) {
423 return;
424 }
425 getUi()
426 .setEndCallButtonEnabled(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700427 mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700428 != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800429 true /* shouldAnimate */);
430 updatePrimaryCallState();
431 }
432
Eric Erfanianccca3152017-02-22 16:32:36 -0800433 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
434 if (mPrimary == null) {
435 return false;
436 }
437 return primaryChanged
438 || mInCallScreen.isManageConferenceVisible() != shouldShowManageConference();
439 }
440
441 private void updatePrimaryCallState() {
442 if (getUi() != null && mPrimary != null) {
443 boolean isWorkCall =
444 mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL)
445 || (mPrimaryContactInfo != null
446 && mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
447 boolean isHdAudioCall =
448 isPrimaryCallActive() && mPrimary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700449 boolean isAttemptingHdAudioCall =
450 !isHdAudioCall
451 && !mPrimary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
452 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(mContext);
453
454 boolean isBusiness = mPrimaryContactInfo != null && mPrimaryContactInfo.isBusiness;
455
Eric Erfanianccca3152017-02-22 16:32:36 -0800456 // Check for video state change and update the visibility of the contact photo. The contact
457 // photo is hidden when the incoming video surface is shown.
458 // The contact photo visibility can also change in setPrimary().
459 boolean shouldShowContactPhoto =
460 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
461 getUi()
462 .setCallState(
463 new PrimaryCallState(
464 mPrimary.getState(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700465 mPrimary.isVideoCall(),
466 mPrimary.getVideoTech().getSessionModificationState(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800467 mPrimary.getDisconnectCause(),
468 getConnectionLabel(),
469 getCallStateIcon(),
470 getGatewayNumber(),
471 shouldShowCallSubject(mPrimary) ? mPrimary.getCallSubject() : null,
wangqi97539352017-09-25 11:15:16 -0700472 PhoneNumberHelper.formatNumber(
473 mPrimary.getCallbackNumber(), mPrimary.getSimCountryIso()),
Eric Erfanianccca3152017-02-22 16:32:36 -0800474 mPrimary.hasProperty(Details.PROPERTY_WIFI),
Eric Erfanian83b20212017-05-31 08:53:10 -0700475 mPrimary.isConferenceCall()
476 && !mPrimary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE),
Eric Erfanianccca3152017-02-22 16:32:36 -0800477 isWorkCall,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700478 isAttemptingHdAudioCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800479 isHdAudioCall,
480 !TextUtils.isEmpty(mPrimary.getLastForwardedNumber()),
481 shouldShowContactPhoto,
482 mPrimary.getConnectTimeMillis(),
wangqi9982f0d2017-10-11 17:46:07 -0700483 mPrimary.isVoiceMailNumber(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700484 mPrimary.isRemotelyHeld(),
Eric Erfanian2ca43182017-08-31 06:57:16 -0700485 isBusiness,
486 supports2ndCallOnHold(),
487 getSwapToSecondaryButtonState(),
488 mPrimary.isAssistedDialed(),
erfaniand0f207f2017-10-11 12:23:29 -0700489 null,
490 mPrimary.getAssistedDialingExtras()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800491
492 InCallActivity activity =
493 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
494 if (activity != null) {
495 activity.onPrimaryCallStateChanged();
496 }
497 }
498 }
499
Eric Erfanian2ca43182017-08-31 06:57:16 -0700500 private @ButtonState int getSwapToSecondaryButtonState() {
501 if (mSecondary == null) {
502 return ButtonState.NOT_SUPPORT;
503 }
504 if (mPrimary.getState() == State.ACTIVE) {
505 return ButtonState.ENABLED;
506 }
507 return ButtonState.DISABLED;
508 }
509
Eric Erfanianccca3152017-02-22 16:32:36 -0800510 /** Only show the conference call button if we can manage the conference. */
511 private void maybeShowManageConferenceCallButton() {
512 getUi().showManageConferenceCallButton(shouldShowManageConference());
513 }
514
515 /**
516 * Determines if the manage conference button should be visible, based on the current primary
517 * call.
518 *
519 * @return {@code True} if the manage conference button should be visible.
520 */
521 private boolean shouldShowManageConference() {
522 if (mPrimary == null) {
523 return false;
524 }
525
526 return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)
527 && !mIsFullscreen;
528 }
529
Eric Erfanian2ca43182017-08-31 06:57:16 -0700530 private boolean supports2ndCallOnHold() {
531 DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
532 DialerCall incomingCall = CallList.getInstance().getIncomingCall();
533 if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
534 return incomingCall.can(Details.CAPABILITY_HOLD);
535 }
536 return true;
537 }
538
Eric Erfanianccca3152017-02-22 16:32:36 -0800539 @Override
540 public void onCallStateButtonClicked() {
541 Intent broadcastIntent = Bindings.get(mContext).getCallStateButtonBroadcastIntent(mContext);
542 if (broadcastIntent != null) {
543 LogUtil.v(
544 "CallCardPresenter.onCallStateButtonClicked",
545 "sending call state button broadcast: " + broadcastIntent);
546 mContext.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
547 }
548 }
549
550 @Override
551 public void onManageConferenceClicked() {
552 InCallActivity activity =
553 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
554 activity.showConferenceFragment(true);
555 }
556
557 @Override
558 public void onShrinkAnimationComplete() {
559 InCallPresenter.getInstance().onShrinkAnimationComplete();
560 }
561
Eric Erfanianccca3152017-02-22 16:32:36 -0800562 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
563 // no need to start search for conference calls which show generic info.
564 if (call != null && !call.isConferenceCall()) {
565 startContactInfoSearch(call, isPrimary, call.getState() == DialerCall.State.INCOMING);
566 }
567 }
568
Eric Erfanianccca3152017-02-22 16:32:36 -0800569 /** Starts a query for more contact data for the save primary and secondary calls. */
570 private void startContactInfoSearch(
571 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
572 final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
573
574 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
575 }
576
577 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
578 final boolean entryMatchesExistingCall =
579 (isPrimary && mPrimary != null && TextUtils.equals(callId, mPrimary.getId()))
580 || (!isPrimary && mSecondary != null && TextUtils.equals(callId, mSecondary.getId()));
581 if (entryMatchesExistingCall) {
582 updateContactEntry(entry, isPrimary);
583 } else {
584 LogUtil.e(
585 "CallCardPresenter.onContactInfoComplete",
586 "dropping stale contact lookup info for " + callId);
587 }
588
589 final DialerCall call = CallList.getInstance().getCallById(callId);
590 if (call != null) {
591 call.getLogState().contactLookupResult = entry.contactLookupResult;
592 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700593 if (entry.lookupUri != null) {
594 CallerInfoUtils.sendViewNotification(mContext, entry.lookupUri);
Eric Erfanianccca3152017-02-22 16:32:36 -0800595 }
596 }
597
598 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
599 if (getUi() == null) {
600 return;
601 }
602
603 if (entry.photo != null) {
604 if (mPrimary != null && callId.equals(mPrimary.getId())) {
605 updateContactEntry(entry, true /* isPrimary */);
606 } else if (mSecondary != null && callId.equals(mSecondary.getId())) {
607 updateContactEntry(entry, false /* isPrimary */);
608 }
609 }
610 }
611
612 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
613 if (isPrimary) {
614 mPrimaryContactInfo = entry;
615 updatePrimaryDisplayInfo();
616 } else {
617 mSecondaryContactInfo = entry;
618 updateSecondaryDisplayInfo();
619 }
620 }
621
622 /**
623 * Get the highest priority call to display. Goes through the calls and chooses which to return
624 * based on priority of which type of call to display to the user. Callers can use the "ignore"
625 * feature to get the second best call by passing a previously found primary call as ignore.
626 *
627 * @param ignore A call to ignore if found.
628 */
629 private DialerCall getCallToDisplay(
630 CallList callList, DialerCall ignore, boolean skipDisconnected) {
631 // Active calls come second. An active call always gets precedent.
632 DialerCall retval = callList.getActiveCall();
633 if (retval != null && retval != ignore) {
634 return retval;
635 }
636
637 // Sometimes there is intemediate state that two calls are in active even one is about
638 // to be on hold.
639 retval = callList.getSecondActiveCall();
640 if (retval != null && retval != ignore) {
641 return retval;
642 }
643
644 // Disconnected calls get primary position if there are no active calls
645 // to let user know quickly what call has disconnected. Disconnected
646 // calls are very short lived.
647 if (!skipDisconnected) {
648 retval = callList.getDisconnectingCall();
649 if (retval != null && retval != ignore) {
650 return retval;
651 }
652 retval = callList.getDisconnectedCall();
653 if (retval != null && retval != ignore) {
654 return retval;
655 }
656 }
657
658 // Then we go to background call (calls on hold)
659 retval = callList.getBackgroundCall();
660 if (retval != null && retval != ignore) {
661 return retval;
662 }
663
664 // Lastly, we go to a second background call.
665 retval = callList.getSecondBackgroundCall();
666
667 return retval;
668 }
669
670 private void updatePrimaryDisplayInfo() {
671 if (mInCallScreen == null) {
672 // TODO: May also occur if search result comes back after ui is destroyed. Look into
673 // removing that case completely.
674 LogUtil.v(
675 "CallCardPresenter.updatePrimaryDisplayInfo",
676 "updatePrimaryDisplayInfo called but ui is null!");
677 return;
678 }
679
680 if (mPrimary == null) {
681 // Clear the primary display info.
682 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
683 return;
684 }
685
686 // Hide the contact photo if we are in a video call and the incoming video surface is
687 // showing.
688 boolean showContactPhoto =
689 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
690
691 // DialerCall placed through a work phone account.
692 boolean hasWorkCallProperty = mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL);
693
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700694 MultimediaData multimediaData = null;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700695 if (mPrimary.getEnrichedCallSession() != null) {
696 multimediaData = mPrimary.getEnrichedCallSession().getMultimediaData();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700697 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800698
699 if (mPrimary.isConferenceCall()) {
700 LogUtil.v(
701 "CallCardPresenter.updatePrimaryDisplayInfo",
702 "update primary display info for conference call.");
703
704 mInCallScreen.setPrimary(
705 new PrimaryInfo(
706 null /* number */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700707 CallerInfoUtils.getConferenceString(
708 mContext, mPrimary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800709 false /* nameIsNumber */,
710 null /* location */,
711 null /* label */,
Eric Erfanian83b20212017-05-31 08:53:10 -0700712 null /* photo */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800713 ContactPhotoType.DEFAULT_PLACEHOLDER,
714 false /* isSipCall */,
715 showContactPhoto,
716 hasWorkCallProperty,
717 false /* isSpam */,
wangqiae6c8ec2017-09-28 17:39:40 -0700718 false /* isLocalContact */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800719 false /* answeringDisconnectsOngoingCall */,
720 shouldShowLocation(),
721 null /* contactInfoLookupKey */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700722 null /* enrichedCallMultimediaData */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700723 true /* showInCallButtonGrid */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700724 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800725 } else if (mPrimaryContactInfo != null) {
726 LogUtil.v(
727 "CallCardPresenter.updatePrimaryDisplayInfo",
728 "update primary display info for " + mPrimaryContactInfo);
729
730 String name = getNameForCall(mPrimaryContactInfo);
731 String number;
732
733 boolean isChildNumberShown = !TextUtils.isEmpty(mPrimary.getChildNumber());
734 boolean isForwardedNumberShown = !TextUtils.isEmpty(mPrimary.getLastForwardedNumber());
735 boolean isCallSubjectShown = shouldShowCallSubject(mPrimary);
736
737 if (isCallSubjectShown) {
738 number = null;
739 } else if (isChildNumberShown) {
740 number = mContext.getString(R.string.child_number, mPrimary.getChildNumber());
741 } else if (isForwardedNumberShown) {
742 // Use last forwarded number instead of second line, if present.
743 number = mPrimary.getLastForwardedNumber();
744 } else {
745 number = mPrimaryContactInfo.number;
746 }
747
748 boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700749
Eric Erfanianccca3152017-02-22 16:32:36 -0800750 // DialerCall with caller that is a work contact.
751 boolean isWorkContact = (mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
752 mInCallScreen.setPrimary(
753 new PrimaryInfo(
754 number,
Eric Erfanianc857f902017-05-15 14:05:33 -0700755 mPrimary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800756 nameIsNumber,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700757 shouldShowLocationAsLabel(nameIsNumber, mPrimaryContactInfo.shouldShowLocation)
758 ? mPrimaryContactInfo.location
759 : null,
Eric Erfanianccca3152017-02-22 16:32:36 -0800760 isChildNumberShown || isCallSubjectShown ? null : mPrimaryContactInfo.label,
761 mPrimaryContactInfo.photo,
762 mPrimaryContactInfo.photoType,
763 mPrimaryContactInfo.isSipCall,
764 showContactPhoto,
765 hasWorkCallProperty || isWorkContact,
766 mPrimary.isSpam(),
wangqiae6c8ec2017-09-28 17:39:40 -0700767 mPrimaryContactInfo.isLocalContact(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800768 mPrimary.answeringDisconnectsForegroundVideoCall(),
769 shouldShowLocation(),
770 mPrimaryContactInfo.lookupKey,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700771 multimediaData,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700772 true /* showInCallButtonGrid */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700773 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800774 } else {
775 // Clear the primary display info.
776 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
777 }
778
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700779 if (isInCallScreenReady) {
780 mInCallScreen.showLocationUi(getLocationFragment());
781 } else {
782 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
783 }
784 }
785
Eric Erfaniand8046e52017-04-06 09:41:50 -0700786 private static boolean shouldShowLocationAsLabel(
787 boolean nameIsNumber, boolean shouldShowLocation) {
788 if (nameIsNumber) {
789 return true;
790 }
791 if (shouldShowLocation) {
792 return true;
793 }
794 return false;
795 }
796
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700797 private Fragment getLocationFragment() {
798 if (!ConfigProviderBindings.get(mContext)
799 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
800 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
801 return null;
802 }
803 if (!shouldShowLocation()) {
804 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
805 return null;
806 }
807 if (!hasLocationPermission()) {
808 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
809 return null;
810 }
811 if (isBatteryTooLowForEmergencyLocation()) {
812 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
813 return null;
814 }
815 if (ActivityCompat.isInMultiWindowMode(mInCallScreen.getInCallScreenFragment().getActivity())) {
816 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
817 return null;
818 }
819 if (mPrimary.isVideoCall()) {
820 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
821 return null;
822 }
823 if (!callLocation.canGetLocation(mContext)) {
824 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
825 return null;
826 }
827 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
828 return callLocation.getLocationFragment(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800829 }
830
831 private boolean shouldShowLocation() {
832 if (isOutgoingEmergencyCall(mPrimary)) {
833 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
834 return true;
835 } else if (isIncomingEmergencyCall(mPrimary)) {
836 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
837 return true;
838 } else if (isIncomingEmergencyCall(mSecondary)) {
839 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
840 return true;
841 }
842 return false;
843 }
844
845 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
846 return call != null && !call.isIncoming() && call.isEmergencyCall();
847 }
848
849 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
850 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
851 }
852
853 private boolean hasLocationPermission() {
854 return ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION)
855 == PackageManager.PERMISSION_GRANTED;
856 }
857
858 private boolean isBatteryTooLowForEmergencyLocation() {
859 Intent batteryStatus =
860 mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
861 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
862 if (status == BatteryManager.BATTERY_STATUS_CHARGING
863 || status == BatteryManager.BATTERY_STATUS_FULL) {
864 // Plugged in or full battery
865 return false;
866 }
867 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
868 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
869 float batteryPercent = (100f * level) / scale;
870 long threshold =
871 ConfigProviderBindings.get(mContext)
872 .getLong(
873 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
874 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
875 LogUtil.i(
876 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
877 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
878 return batteryPercent < threshold;
879 }
880
881 private void updateSecondaryDisplayInfo() {
882 if (mInCallScreen == null) {
883 return;
884 }
885
886 if (mSecondary == null) {
887 // Clear the secondary display info.
888 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
889 return;
890 }
891
Eric Erfanian2ca43182017-08-31 06:57:16 -0700892 if (mSecondary.isMergeInProcess()) {
893 LogUtil.i(
894 "CallCardPresenter.updateSecondaryDisplayInfo",
895 "secondary call is merge in process, clearing info");
896 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
897 return;
898 }
899
Eric Erfanianccca3152017-02-22 16:32:36 -0800900 if (mSecondary.isConferenceCall()) {
901 mInCallScreen.setSecondary(
902 new SecondaryInfo(
903 true /* show */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700904 CallerInfoUtils.getConferenceString(
905 mContext, mSecondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800906 false /* nameIsNumber */,
907 null /* label */,
908 mSecondary.getCallProviderLabel(),
909 true /* isConference */,
910 mSecondary.isVideoCall(),
911 mIsFullscreen));
912 } else if (mSecondaryContactInfo != null) {
913 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + mSecondaryContactInfo);
914 String name = getNameForCall(mSecondaryContactInfo);
915 boolean nameIsNumber = name != null && name.equals(mSecondaryContactInfo.number);
916 mInCallScreen.setSecondary(
917 new SecondaryInfo(
918 true /* show */,
Eric Erfanianc857f902017-05-15 14:05:33 -0700919 mSecondary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800920 nameIsNumber,
921 mSecondaryContactInfo.label,
922 mSecondary.getCallProviderLabel(),
923 false /* isConference */,
924 mSecondary.isVideoCall(),
925 mIsFullscreen));
926 } else {
927 // Clear the secondary display info.
928 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
929 }
930 }
931
932 /** Returns the gateway number for any existing outgoing call. */
933 private String getGatewayNumber() {
934 if (hasOutgoingGatewayCall()) {
935 return DialerCall.getNumberFromHandle(mPrimary.getGatewayInfo().getGatewayAddress());
936 }
937 return null;
938 }
939
940 /**
941 * Returns the label (line of text above the number/name) for any given call. For example,
942 * "calling via [Account/Google Voice]" for outgoing calls.
943 */
944 private String getConnectionLabel() {
945 if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_PHONE_STATE)
946 != PackageManager.PERMISSION_GRANTED) {
947 return null;
948 }
949 StatusHints statusHints = mPrimary.getStatusHints();
950 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
951 return statusHints.getLabel().toString();
952 }
953
954 if (hasOutgoingGatewayCall() && getUi() != null) {
955 // Return the label for the gateway app on outgoing calls.
956 final PackageManager pm = mContext.getPackageManager();
957 try {
958 ApplicationInfo info =
959 pm.getApplicationInfo(mPrimary.getGatewayInfo().getGatewayProviderPackageName(), 0);
960 return pm.getApplicationLabel(info).toString();
961 } catch (PackageManager.NameNotFoundException e) {
962 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
963 return null;
964 }
965 }
966 return mPrimary.getCallProviderLabel();
967 }
968
969 private Drawable getCallStateIcon() {
970 // Return connection icon if one exists.
971 StatusHints statusHints = mPrimary.getStatusHints();
972 if (statusHints != null && statusHints.getIcon() != null) {
973 Drawable icon = statusHints.getIcon().loadDrawable(mContext);
974 if (icon != null) {
975 return icon;
976 }
977 }
978
979 return null;
980 }
981
982 private boolean hasOutgoingGatewayCall() {
983 // We only display the gateway information while STATE_DIALING so return false for any other
984 // call state.
985 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
986 // is also called after a contact search completes (call is not present yet). Split the
987 // UI update so it can receive independent updates.
988 if (mPrimary == null) {
989 return false;
990 }
991 return DialerCall.State.isDialing(mPrimary.getState())
992 && mPrimary.getGatewayInfo() != null
993 && !mPrimary.getGatewayInfo().isEmpty();
994 }
995
996 /** Gets the name to display for the call. */
Eric Erfanian2ca43182017-08-31 06:57:16 -0700997 private String getNameForCall(ContactCacheEntry contactInfo) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800998 String preferredName =
999 ContactDisplayUtils.getPreferredDisplayName(
1000 contactInfo.namePrimary, contactInfo.nameAlternative, mContactsPreferences);
1001 if (TextUtils.isEmpty(preferredName)) {
1002 return contactInfo.number;
1003 }
1004 return preferredName;
1005 }
1006
Eric Erfanianccca3152017-02-22 16:32:36 -08001007 @Override
1008 public void onSecondaryInfoClicked() {
1009 if (mSecondary == null) {
1010 LogUtil.e(
1011 "CallCardPresenter.onSecondaryInfoClicked",
1012 "secondary info clicked but no secondary call.");
1013 return;
1014 }
1015
Eric Erfanian2ca43182017-08-31 06:57:16 -07001016 Logger.get(mContext)
1017 .logCallImpression(
1018 DialerImpression.Type.IN_CALL_SWAP_SECONDARY_BUTTON_PRESSED,
1019 mPrimary.getUniqueCallId(),
1020 mPrimary.getTimeAddedMs());
Eric Erfanianccca3152017-02-22 16:32:36 -08001021 LogUtil.i(
1022 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + mSecondary);
1023 mSecondary.unhold();
1024 }
1025
1026 @Override
1027 public void onEndCallClicked() {
1028 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + mPrimary);
1029 if (mPrimary != null) {
1030 mPrimary.disconnect();
1031 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001032 PostCall.onDisconnectPressed(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -08001033 }
1034
1035 /**
1036 * Handles a change to the fullscreen mode of the in-call UI.
1037 *
1038 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
1039 */
1040 @Override
1041 public void onFullscreenModeChanged(boolean isFullscreenMode) {
1042 mIsFullscreen = isFullscreenMode;
1043 if (mInCallScreen == null) {
1044 return;
1045 }
1046 maybeShowManageConferenceCallButton();
1047 }
1048
1049 private boolean isPrimaryCallActive() {
1050 return mPrimary != null && mPrimary.getState() == DialerCall.State.ACTIVE;
1051 }
1052
Eric Erfanianccca3152017-02-22 16:32:36 -08001053 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1054 if (primary == null) {
1055 return false;
1056 }
1057 if ((!DialerCall.State.isConnectingOrConnected(callState)
1058 && callState != DialerCall.State.DISCONNECTING
1059 && callState != DialerCall.State.DISCONNECTED)
1060 || callState == DialerCall.State.INCOMING) {
1061 return false;
1062 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001063 if (mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001064 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001065 return false;
1066 }
1067 return true;
1068 }
1069
1070 @Override
1071 public void onInCallScreenResumed() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001072 updatePrimaryDisplayInfo();
1073
Eric Erfanianccca3152017-02-22 16:32:36 -08001074 if (shouldSendAccessibilityEvent) {
1075 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1076 }
1077 }
1078
Eric Erfaniand8046e52017-04-06 09:41:50 -07001079 @Override
Eric Erfanian2ca43182017-08-31 06:57:16 -07001080 public void onInCallScreenPaused() {}
Eric Erfaniand8046e52017-04-06 09:41:50 -07001081
Eric Erfanianccca3152017-02-22 16:32:36 -08001082 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1083 AccessibilityManager am =
1084 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1085 if (!am.isEnabled()) {
1086 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1087 return false;
1088 }
1089 if (inCallScreen == null) {
1090 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1091 return false;
1092 }
1093 Fragment fragment = inCallScreen.getInCallScreenFragment();
1094 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1095 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1096 return false;
1097 }
1098
1099 DisplayManager displayManager =
1100 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1101 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1102 boolean screenIsOn = display.getState() == Display.STATE_ON;
1103 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1104 if (!screenIsOn) {
1105 return false;
1106 }
1107
1108 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1109 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1110 View view = inCallScreen.getInCallScreenFragment().getView();
1111 view.getParent().requestSendAccessibilityEvent(view, event);
1112 return true;
1113 }
1114
1115 private void maybeSendAccessibilityEvent(
1116 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1117 shouldSendAccessibilityEvent = false;
1118 if (mContext == null) {
1119 return;
1120 }
1121 final AccessibilityManager am =
1122 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
1123 if (!am.isEnabled()) {
1124 return;
1125 }
1126 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1127 // due to switching calls between two ongoing calls (one is on hold).
1128 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1129 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1130 || primaryChanged) {
1131 LogUtil.i(
1132 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1133 shouldSendAccessibilityEvent = true;
1134 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1135 }
1136 }
1137
1138 /**
1139 * Determines whether the call subject should be visible on the UI. For the call subject to be
1140 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1141 *
1142 * @param call The call.
1143 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1144 */
1145 private boolean shouldShowCallSubject(DialerCall call) {
1146 if (call == null) {
1147 return false;
1148 }
1149
1150 boolean isIncomingOrWaiting =
1151 mPrimary.getState() == DialerCall.State.INCOMING
1152 || mPrimary.getState() == DialerCall.State.CALL_WAITING;
1153 return isIncomingOrWaiting
1154 && !TextUtils.isEmpty(call.getCallSubject())
1155 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1156 && call.isCallSubjectSupported();
1157 }
1158
1159 /**
1160 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1161 * call with a subject.
1162 *
1163 * @param call The call
1164 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1165 */
1166 private boolean shouldShowNoteSentToast(DialerCall call) {
1167 return call != null
1168 && hasCallSubject(call)
1169 && (call.getState() == DialerCall.State.DIALING
1170 || call.getState() == DialerCall.State.CONNECTING);
1171 }
1172
1173 private InCallScreen getUi() {
1174 return mInCallScreen;
1175 }
1176
1177 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1178
1179 private final WeakReference<CallCardPresenter> mCallCardPresenter;
1180 private final boolean mIsPrimary;
1181
1182 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
1183 mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1184 mIsPrimary = isPrimary;
1185 }
1186
1187 @Override
1188 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
1189 CallCardPresenter presenter = mCallCardPresenter.get();
1190 if (presenter != null) {
1191 presenter.onContactInfoComplete(callId, entry, mIsPrimary);
1192 }
1193 }
1194
1195 @Override
1196 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
1197 CallCardPresenter presenter = mCallCardPresenter.get();
1198 if (presenter != null) {
1199 presenter.onImageLoadComplete(callId, entry);
1200 }
1201 }
1202 }
1203}